ข้าม​ไป​ยัง​เนื้อหา

สัญญา​ของ​ขอบ เขียน​เป็น​ตาราง แล้ว​บังคับ​ด้วย​เทสต์

เจ็ด​บท​ที่​ผ่าน​มา​ให้​ข้อ​เท็จ​จริง​มา​เป็น​ชุด · บท​นี้​รวบ​มัน​เป็น​ของ​ชิ้น​เดียว​ที่​ส่ง​ต่อ​ได้

ซ้าย​มือ​คือ​เหตุการณ์​ข้าง​ใน ขวา​มือ​คือ​สิ่ง​ที่​ผู้​เรียก​ได้​รับ

เหตุการณ์​ข้าง​ในรหัสcontent-typebody บอก​อะไรลอง​ใหม่​ช่วย​ไหม
คำขอ​ถูก​ต้อง200application/jsonid ของ​ใบ​ที่​จอง—
ส่ง​ซ้ำ​ด้วย​คีย์​เดิม200application/jsonid ตัว​เดิม กับ replayedไม่​ต้อง​ลอง
กรอก​ผิด400application/problem+jsonerrors ครบ​ทุก​ช่อง​ที่​ผิดต้อง​แก้​ก่อน
ชน​กับ​ใบ​ที่​มี​อยู่409application/problem+jsonretryable เป็น falseไม่​ช่วย
ระบบ​พัง​จริง500application/jsonไม่​บอก​อะไร​เกิน​รหัสอาจ​ช่วย

แถว​สุดท้าย​เป็น​แถว​เดียว​ที่​เรา​ไม่​ได้​แก้ · และ​นั่น​คือ​ความ​ตั้งใจ

ความ​ล้ม​เหลว​ที่​เรา​ไม่​ได้​คาด​ไว้ ไม่​ควร​พ่น​ราย​ละเอียด​ข้าง​ใน​ออก​ไป​ให้​ใคร​ก็ได้​อ่าน · สิ่ง​ที่​ผู้​เรียก​ต้อง​รู้​จาก​แถว​นั้น​มี​ข้อ​เดียว คือ​ปัญหา​อยู่​ฝั่ง​เรา

มัน​คือ​ความ​ตั้งใจ · และ​ความ​ตั้งใจ​ไม่มี​อะไร​บังคับ

นี่​คือ​คำถาม​เดียว​กับ​ที่​คอร์ส​ก่อน​เปิด​หัว​ไว้ · เอกสาร​ออกแบบ​ไม่​บังคับ​อะไร

ตาราง​ข้าง​บน​จะ​เป็น boundary contractboundary contractตาราง​ที่​บอก​ว่า​เหตุการณ์​แต่ละ​อย่าง​ข้าง​ใน​จะ​ออก​ไป​เป็น​รหัส​อะไร​และ body รูป​ไหน · เขียน​ไว้​เฉยๆ คือ​ความ​ตั้งใจ · บังคับ​ด้วย​เทสต์​แล้ว​จึง​เป็น​สัญญา ก็​ต่อ​เมื่อ​มี​อะไร​สัก​อย่าง​ที่​แดง​ขึ้น​มา เมื่อ​มี​คน​ทำให้​มัน​ไม่​จริง

app/src/test/kotlin/clinic/http/BoundaryContractTest.kt
package clinic.http
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.SpringBootTest
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* สัญญาของขอบ · หนึ่งแถวในตารางของบทที่ 8 คือหนึ่งข้อทดสอบ
*
* เทสต์นี้ไม่ได้ตรวจว่าระบบข้างในทำงานถูก · มันตรวจว่าสิ่งที่เราพูดกับผู้เรียก
* ตรงกับที่สัญญาไว้ · ทุกข้อใช้ช่วงเวลาของตัวเอง จึงไม่ขึ้นกับลำดับที่ JUnit เลือกรัน
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BoundaryContractTest {
@Value("\${local.server.port}")
var port: Int = 0
private fun post(json: String, key: String? = null): Pair<Int, String> {
val b = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/bookings"))
.header("content-type", "application/json")
if (key != null) b.header("Idempotency-Key", key)
val r = HttpClient.newHttpClient().send(
b.POST(HttpRequest.BodyPublishers.ofString(json)).build(),
HttpResponse.BodyHandlers.ofString(),
)
return r.statusCode() to r.body()
}
@Test
fun `คำขอที่ถูกต้อง ได้ 200`() {
val (status, _) = post("""{"doctorId":"d-contract","startMinute":700,"endMinute":730}""")
assertEquals(200, status)
}
@Test
fun `คำขอที่กรอกผิด ได้ 400 พร้อมชื่อช่องที่ผิด`() {
val (status, body) = post("""{"doctorId":"","startMinute":-5,"endMinute":0}""")
assertEquals(400, status)
assertTrue(body.contains("\"errors\""), body)
assertTrue(body.contains("doctorId"), body)
}
@Test
fun `คำขอที่ชนของเดิม ได้ 409 และบอกว่าลองใหม่ไม่ช่วย`() {
post("""{"doctorId":"d-contract","startMinute":800,"endMinute":830}""")
val (status, body) =
post("""{"doctorId":"d-contract","startMinute":810,"endMinute":840}""")
assertEquals(409, status)
assertTrue(body.contains("\"retryable\":false"), body)
}
@Test
fun `คำขอเดิมที่ส่งซ้ำด้วยคีย์เดิม ได้ 200 และ id ตัวเดิม`() {
val body = """{"doctorId":"d-contract","startMinute":900,"endMinute":930}"""
val (firstStatus, first) = post(body, "contract-1")
val (againStatus, again) = post(body, "contract-1")
assertEquals(200, firstStatus)
assertEquals(200, againStatus)
assertEquals(first.substringAfter("\"id\":").substringBefore(","),
again.substringAfter("\"id\":").substringBefore(","))
assertTrue(again.contains("\"replayed\":true"), again)
}
}
🎯 ทุก​ข้อ​ใช้​ช่วง​เวลา​ของ​ตัว​เอง

สี่​ข้อ​นี้​รัน​ใน​ลำดับ​ไหน​ก็ได้ · JUnit ไม่​รับ​ประกัน​ลำดับ และ​เรา​ไม่​ควร​พึ่ง​มัน

ร่าง​แรก​ของ​เทสต์​ชุด​นี้​ใช้​ช่วง​เวลา​เดียวกัน​สอง​ข้อ · ข้อ​ที่​รัน​ทีหลัง​จึง​ชน​กับ​แถว​ที่ ข้อ​แรก​สร้าง​ไว้ แล้ว​แดง​ด้วย​เหตุผล​ที่​ไม่​เกี่ยว​กับ​สิ่ง​ที่​มัน​ตรวจ

เทสต์​ที่​แดง​ด้วย​เหตุผล​อื่น ไม่​ได้​บอก​อะไร​เกี่ยว​กับ​สัญญา​เลย

ต้อง​เขียน​ไว้​ให้​ชัด​พอ​กับ​สิ่ง​ที่​มัน​ครอบคลุม

  • ใคร​เรียก​ได้​บ้าง ทั้ง​คอร์ส​ไม่​แตะ​การ​ยืนยัน​ตัว​ตน​กับ​สิทธิ์​เลย · ตาราง​ข้าง​บน​บอก​ว่า​เรา​ตอบ​อะไร ไม่​ได้​บอก​ว่า​เรา​ควร​ตอบ​ให้​ใคร
  • คำ​ตอบ​ของ​ครั้ง​แรก​ไม่​ได้​ถูก​เก็บ​ไว้ Idempotency-KeyIdempotency-Keyหัวข้อ​ความ​ที่​ผู้​เรียก​ใช้​บอก​ว่า​คำขอ​นี้​เป็น​ตัว​เดิม​กับ​ที่​เคย​ส่ง · เป็น​สิ่ง​เดียว​ที่​แยก 'คำขอ​เดิม​ที่​ส่ง​ซ้ำ' ออก​จาก 'คำขอ​ใหม่​ที่​บังเอิญ​เหมือน​กัน' ได้ ใน​บท​ที่ 5 ใช้​ค้นหา​แถว แล้ว​ประกอบ​คำ​ตอบ​ใหม่ · ถ้า​คำขอ​แรก​ล้ม​กลาง​ทาง คีย์​นั้น​จะ​ไม่มี​แถว​ให้​ค้น​เจอ
  • ช่อง​ว่าง​ระหว่าง​ค้น​กับ​บันทึก คำขอ​สอง​ใบ​ที่​มี​คีย์​เดียวกัน​และ​เข้า​มา​พร้อม​กัน​จริงๆ ใบ​หลัง​จะ​ได้ 409 แทนที่​จะ​ได้​ผล​เดิม · unique index กัน​ข้อมูล​ซ้ำ​ได้ แต่​กัน​คำ​ตอบ​ที่ ไม่​สม่ำเสมอ​ไม่​ได้
  • ความเร็ว ทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​สัก​ตัว ตาม​ที่​ประกาศ​ไว้​ตั้งแต่​หน้า​แรก

รายการ​นี้​ไม่ใช่​หนี้​ที่​ต้อง​รีบ​ใช้ · มัน​คือ​ขอบเขต​ที่​ประกาศ​ไว้ · สัญญา​ที่​ไม่​บอก​ว่า​ตัว​เอง​ไม่​ครอบ​อะไร คือ​สัญญา​ที่​ถูก​อ่าน​กว้าง​เกิน​จริง​เสมอ

ความ​ถูก​ต้อง​ที่​คอร์ส​ก่อน​สร้าง​ไว้ เหลือ​อะไร​อยู่​บ้าง​เมื่อ​ออก​ไป​ถึง​ผู้​เรียก

ตอน​เริ่ม​คอร์ส คำ​ตอบ​คือ​เกือบ​ไม่​เหลือ​อะไร​เลย · คำขอ​ที่​ผิด​กับ​ระบบ​ที่​พัง​จริง​มี​รูป​เดียวกัน และ​ความ​ขัด​แย้ง​ที่​ฐาน​ข้อมูล​กัน​ไว้​ได้​สำเร็จ ออก​ไป​เป็น​คำ​ว่า “เรา​พัง”

ตอน​นี้​ทั้ง​ห้า​เหตุการณ์​ใน​ตาราง​แยก​ออก​จาก​กัน​ได้​จาก​ฝั่ง​ผู้​เรียก · และ​มี​เทสต์​ที่​แดง​เมื่อ​มี​ใคร​ทำให้​ข้อ​ไหน​ไม่​จริง

boundaryboundaryเส้น​ที่​ระบบ​ของ​เรา​จบ และ​ผู้​เรียก​ที่​เรา​ควบคุม​ไม่​ได้​เริ่ม · ทุก​อย่าง​ที่​ข้าม​เส้น​นี้​ออก​ไป มี​แค่​รหัส หัวข้อ​ความ และ body เท่านั้น · สิ่ง​ที่​ถูก​ต้อง​อยู่​ข้าง​ใน​แต่​ไม่​ได้​ข้าม​ออก​มา ผู้​เรียก​ไม่มี​ทาง​รู้ ไม่ใช่​ที่​ที่​ความ​รู้​ของ​เรา​หยุด​เดิน​ทาง​อีก​ต่อ​ไป · มัน​คือ​ที่​ที่​เรา​เลือก​ว่า​จะ​ให้​อะไร​เดิน​ทาง​ต่อ

🔬 ทุก​อย่าง​ใน​คอร์ส​นี้​ยืนยัน​ซ้ำ​ได้
  • npm run verify:clinic-http สร้าง​โปร​เจ​กต์​ทั้ง​ใบ​ขึ้น​ใหม่ วัด​เจ็ด​ทรง แล้ว​เทียบ​ที​ละ​ช่อง · อยู่​นอก CI เพราะ​ต้อง​ใช้ Docker
  • รหัส​ออก​ของ​มัน​มี​สาม​ความ​หมาย · 0 ข้อ​อ้าง​ยัง​จริง · 1 ข้อ​อ้าง​ผิด · 2 วัด​ไม่​ได้​บน​เครื่อง​นี้ · สอง​อย่าง​หลัง​ต่าง​กัน​มาก และ​ต้อง​อ่าน​ออก​จาก​กัน
  • เทสต์​ใน​บท​นี้​รัน​อยู่​ใน​ทรง​สุดท้าย​ของ probe จริง · ถ้า​มัน​แดง probe ล้ม​ทั้ง​ตัว
  • Spring Boot 4.1.1 · Kotlin 2.4.10 · JDK 25 · PostgreSQL 18 · pg driver 42.7.13

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

ทำไมแถวของ 500 ในตารางสัญญา จึงเป็นแถวเดียวที่คอร์สนี้ไม่แก้