API Testing
ทำไมต้อง Test ที่ชั้น API
API testing ทดสอบ business logic ของ backend โดยตรง โดยไม่ต้องผ่าน UI — เร็วกว่า E2E มาก, ไม่ขึ้นกับการเปลี่ยนแปลงของ UI (CSS/layout เปลี่ยนไม่กระทบเทสต์ API เลย), และคืน feedback ตรงจุดกว่า (รู้ทันทีว่าพังที่ backend logic ไม่ใช่พังที่ frontend render)
E2E ผ่าน UI: คลิกปุ่ม → รอ UI render → เช็คว่าข้อความถูกต้องบนหน้าจอ (ช้า, เปราะกับ UI change)
API testing: ยิง POST /orders ตรงๆ → เช็ค response body/status code (เร็ว, ทนต่อ UI change)
"ทดสอบ API เหมือนตรวจของในโกดังก่อนส่ง ไม่ใช่รอดูที่หน้าร้าน" — ถ้าของในโกดังผิดตั้งแต่ต้น (backend logic ผิด) การตรวจที่หน้าร้าน (UI) จะเจอปัญหาช้ากว่าและบอกจุดผิดได้แค่ "สินค้าไม่ตรง" ไม่บอกว่าผิดตรงไหนในโกดัง
API testing ไม่ได้แทนที่ E2E testing ทั้งหมด — มันทดสอบไม่ได้ว่า UI แสดงข้อมูลจาก API นั้น ถูกต้องจริงไหม (เช่น backend ส่ง field ถูกแต่ frontend map ผิด field) ทีมที่พึ่ง API testing อย่างเดียวโดยตัด E2E ทิ้งหมดอาจพลาดบั๊กประเภทนี้ไปเลย — กลยุทธ์ที่สมดุลคือใช้ API testing เป็น ฐานหลักสำหรับ business logic coverage จำนวนมาก (เร็ว ครอบคลุม edge case ได้มาก) และเก็บ E2E ไว้สำหรับ critical user journey จำนวนน้อยที่ต้องพิสูจน์ว่า "ทั้งระบบต่อกันจริงใช้งานได้" เท่านั้น — ตรงกับหลัก Test Pyramid ที่ API testing มักอยู่ ระดับ integration ซึ่งควรมีปานกลาง ไม่ใช่มากสุดหรือน้อยสุด
HTTP Status Code ที่ต้องรู้แม่น
การเทสต์ API ที่ดีต้องเช็ค status code ให้ตรงความหมาย ไม่ใช่เช็คแค่ "2xx = ผ่าน, ไม่ใช่ 2xx = fail" แบบหยาบๆ
| กลุ่ม | ตัวอย่าง | ความหมาย | สาเหตุทั่วไปที่เทสต์ควรครอบ |
|---|---|---|---|
| 2xx | 200, 201, 204 | สำเร็จ | 201 ตอน create, 204 ตอน delete ไม่มี body |
| 4xx | 400, 401, 403, 404, 409, 422 | ผิดจากฝั่ง client | 400 validation ผิด, 401 ไม่ auth, 403 auth แล้วแต่ไม่มีสิทธิ์, 404 ไม่มี resource, 409 conflict (เช่น ซ้ำ), 422 รูปแบบถูกแต่ความหมายผิด |
| 5xx | 500, 503 | ผิดจากฝั่ง server | ไม่ควรเกิดถ้าโค้ด handle error ถูก — ถ้าเทสต์เจอ 5xx จาก input ที่ควรเป็น 4xx แปลว่า backend ไม่ validate ก่อนให้ exception หลุดออกมา |
"4xx คือลูกค้าทำผิด, 5xx คือร้านทำผิด" — 401/403 ต่างกันตรง 401 คือ "ยังไม่รู้จักคุณเลย" (ไม่ได้ login) ส่วน 403 คือ "รู้จักคุณแล้ว แต่คุณไม่มีสิทธิ์เข้า" (login แล้วแต่ role ไม่พอ)
เทสต์ API ที่ production-grade ต้องเทสต์ "400 vs 422" ให้แยกถูก ซึ่งหลายทีมมองข้าม: 400 Bad Request คือ request ผิดรูปแบบ (เช่น ส่ง JSON ที่ parse ไม่ได้, field ที่ type ผิด เช่น ส่ง string ในที่ที่ต้องเป็น number) ส่วน 422 Unprocessable Entity คือ request ถูกรูปแบบ แต่ ความหมายทางธุรกิจผิด (เช่น ส่งวันที่ check-out ก่อนวันที่ check-in — รูปแบบ JSON ถูกทุกอย่าง แต่ logic ธุรกิจไม่ยอม) การแยกสองกรณีนี้ถูกช่วยให้ client (เช่น frontend หรือ mobile app) เขียน error handling ที่ต่างกันได้แม่นยำกว่า เช่น 400 อาจหมายถึง bug ของฝั่ง client เอง (ควร log/alert dev) ส่วน 422 คือ validation error ปกติที่ต้องโชว์ข้อความให้ user แก้ไข (ไม่ต้อง alert dev)
Contract Testing & Schema Validation
เมื่อระบบเป็น microservices หลาย service คุยกันผ่าน API เทสต์ที่ mock อีกฝั่งไว้ตาม assumption ของตัวเองอาจพังพร้อมกันทั้งคู่โดยไม่รู้ตัว (ดู Test Pyramid → Beyond) Contract testing (เช่น Pact) แก้ปัญหานี้โดยให้ consumer กำหนด "สัญญา" ที่คาดหวังจาก provider แล้ว verify ทั้งสองฝั่งแยกกันเทียบกับสัญญาเดียวกัน โดยไม่ต้อง deploy ทั้งระบบขึ้นมา
Schema validation (เช่น JSON Schema, OpenAPI/Swagger spec) เช็คว่า response ตรงตาม structure ที่ตกลงกันไว้ (field, type, required/optional) — มักใช้คู่กับ contract testing เพื่อ จับ breaking change ก่อน deploy
"Contract test เหมือนเซ็นสัญญาก่อนเริ่มงาน ไม่ใช่เถียงกันตอนงานเสร็จแล้ว" — ทั้งสองฝั่งตรวจกับ สัญญากลางแยกกันได้ ไม่ต้องรอ deploy จริงมาเจอกันแล้วค่อยรู้ว่าเข้าใจกันผิด
Pact ใช้โมเดลที่เรียกว่า "consumer-driven contract" ซึ่งกลับทางจากสัญชาตญาณทั่วไป: ปกติคน คิดว่า provider (คนสร้าง API) ควรเป็นคนกำหนดสัญญา แต่ Pact ให้ consumer (คนเรียกใช้ API) เป็นคนเขียนสัญญาก่อน ตาม field ที่ตัวเองใช้งานจริงเท่านั้น เหตุผลคือ provider เดาไม่ได้ว่า consumer แต่ละตัวใช้ field ไหนจริงบ้าง การให้ consumer ประกาศความคาดหวังเองทำให้ provider สามารถเปลี่ยนแปลง API ส่วนที่ไม่มี consumer คนไหนใช้ได้อย่างปลอดภัย (ไม่ต้องกลัว breaking change) โดยไม่ต้องรอ full regression suite ของทุกฝั่งก่อน deploy ซึ่งเร็วกว่า E2E มาก
API testing exercises backend logic directly, bypassing UI fragility, and sits at the integration level of the test pyramid — it complements rather than replaces E2E, since it can't verify the frontend renders API data correctly. Correctly distinguishing 400 (malformed request) from 422 (well-formed but business-invalid) and 401 (unauthenticated) from 403 (authenticated, unauthorized) lets clients handle errors precisely. Contract testing (e.g., Pact) uses a consumer-driven model — the consumer declares the fields it actually uses, letting providers evolve unused parts of an API safely without full cross-service E2E regression before every deploy.