Test Pyramid
Unit vs Integration vs E2E
Test pyramid คือแบบจำลองที่บอกว่า ควรมีเทสต์ประเภทไหนในสัดส่วนเท่าไหร่ เพื่อให้ได้ทั้ง ความเร็วและความมั่นใจพร้อมกัน
/\
/ \ E2E Tests (น้อยที่สุด, ช้าที่สุด, ครอบคลุม user flow จริง)
/----\
/ \ Integration Tests (ปานกลาง, ครอบ boundary ระหว่าง component)
/--------\
/ \ Unit Tests (เยอะที่สุด, เร็วที่สุด, ครอบ logic ย่อยทุกเส้นทาง)
/------------\
| ระดับ | ทดสอบอะไร | ความเร็ว | จำนวนที่ควรมี |
|---|---|---|---|
| Unit | ฟังก์ชัน/class เดียวแบบแยกตัว (isolated) | มิลลิวินาที | เยอะที่สุด (หลักพัน) |
| Integration | หลาย component ทำงานร่วมกันจริง (เช่น service + database) | วินาที | ปานกลาง (หลักร้อย) |
| E2E | ทั้งระบบทำงานจริงผ่าน user flow (เช่น browser automation) | นาที | น้อยที่สุด (หลักสิบ) |
"ยิ่งฐานกว้าง ยิ่งเร็วยิ่งถูก ยิ่งยอดแหลม ยิ่งช้ายิ่งแพงแต่มั่นใจสุด" — เหมือนตรวจสุขภาพ: ตรวจ เลือดพื้นฐาน (unit test) ทำได้บ่อย ถูก เร็ว ส่วนผ่าตัดตรวจ (E2E) ทำได้นานๆ ครั้ง แพง ใช้เวลานาน แต่ให้ข้อมูลที่ "ครอบคลุมความจริง" มากกว่า
Test pyramid แบบดั้งเดิม (Mike Cohn, 2009) ถูกวิจารณ์มากขึ้นเรื่อยๆ ในยุค microservices ว่า "ไม่สมจริง" เพราะระบบ distributed มักมีจุดบกพร่องที่เกิดจาก การสื่อสารระหว่าง service มากกว่า logic ภายใน service เดียว — แนวคิดใหม่ที่ได้รับความนิยมคือ "Testing Trophy" (Kent C. Dodds) ที่ขยาย integration test ให้ใหญ่กว่า unit test (สลับสัดส่วนจากพีระมิด) โดยให้ เหตุผลว่า integration test ให้ความมั่นใจต่อหน่วยเวลาที่ลงทุนสูงกว่า unit test ที่ mock เยอะเกินจนทดสอบแค่ "implementation detail" ไม่ใช่ "behavior จริงที่ user เจอ" — ไม่มีรูปทรง ไหนถูกต้องสัมบูรณ์ ขึ้นกับลักษณะระบบ (monolith vs microservices, legacy vs greenfield) การรู้ ทั้งสองแนวคิดและอธิบาย trade-off ได้คือสิ่งที่แยกคำตอบ intermediate จาก beginner
Trade-off ของแต่ละระดับ
- Unit test เยอะเกินไป (ไม่มี integration/E2E เลย) → ทุกเทสต์ผ่านแต่ระบบจริงพังเพราะ component เชื่อมกันไม่ถูก (เทสต์แต่ละชิ้นผ่านแต่ "ประกอบร่าง" แล้วพัง)
- E2E เยอะเกินไป (ไม่มี unit test) → เทสต์ช้ามาก, flaky สูง (ขึ้นกับ environment/timing), debug ยากเวลา fail (ไม่รู้ว่าพังตรงไหนในระบบใหญ่)
- Integration test หนักเกิน → ได้ความมั่นใจดี แต่ CI pipeline ช้าลงมากถ้าไม่จัดการ parallelization ให้ดี
"Unit test บอกว่าชิ้นส่วนแต่ละชิ้นดี, Integration test บอกว่าประกอบกันแล้วทำงานได้จริง, E2E บอกว่าลูกค้าใช้งานได้จริง" — ทั้งสามคำถามต่างกัน เทสต์ครบแค่ข้อเดียวตอบไม่ได้ทั้งสามคำถามพร้อมกัน
มี "ความล้มเหลว" ประเภทหนึ่งที่ unit test ไม่มีทางจับได้โดยหลักการ ไม่ว่าจะเขียนดีแค่ไหน:
contract mismatch ระหว่าง service (เช่น service A ส่ง field ชื่อ user_id แต่ service B
คาดหวัง userId) เพราะ unit test ของแต่ละฝั่ง mock อีกฝั่งไว้ตาม assumption ของตัวเอง ถ้า
assumption ทั้งสองฝั่งผิดตรงกัน (ทั้งคู่ mock ผิดเหมือนกัน) unit test จะผ่านหมดแต่ระบบจริงพัง
— นี่คือเหตุผลที่แนวคิด Contract Testing (เช่น Pact) เกิดขึ้นมา: ให้ consumer กำหนด
"สัญญา" ที่ตัวเองคาดหวังจาก provider แล้วรัน verification ทั้งสองฝั่งแยกกันเทียบกับสัญญาเดียวกัน
โดยไม่ต้อง deploy ทั้งระบบขึ้นมาเทสต์จริงแบบ E2E ที่แพงกว่ามาก — เป็นเทคนิคระดับ production ที่
ทีมระดับ intermediate ขึ้นไปควรรู้จักไว้
The test pyramid prescribes many fast unit tests, fewer integration tests, and the fewest, slowest E2E tests. Over-indexing on unit tests risks all-green suites that still fail in production due to component miscommunication; over-indexing on E2E yields slow, flaky, hard-to-debug suites. The "Testing Trophy" (Kent C. Dodds) challenges the traditional pyramid by favoring integration tests for microservice-era systems, arguing they give more confidence per unit of effort than heavily- mocked unit tests. Neither shape is universally correct — it depends on system architecture. Notably, unit tests can never catch cross-service contract mismatches by design, since each side mocks the other based on its own (possibly wrong) assumptions — Contract Testing (e.g. Pact) exists specifically to close this gap without resorting to full E2E tests.