Chiến lược kiểm thử theo rủi ro và boundary
Test suite là một portfolio tín hiệu: mỗi lớp test mua một mức tự tin khác nhau bằng thời gian chạy, độ trung thực với production và chi phí chẩn đoán khi thất bại.
1. Mental model: test là cơ chế kiểm soát rủi ro
Đừng bắt đầu bằng câu hỏi “nên dùng annotation nào?” hoặc “cần bao nhiêu phần trăm unit test?”. Hãy bắt đầu từ failure có thể gây thiệt hại: sai business invariant, mất dữ liệu, contract không tương thích, duplicate side effect, race condition, cấu hình framework sai hoặc deployment hỏng.
Sau khi xác định rủi ro, chọn boundary nhỏ nhất nhưng vẫn tái hiện trung thực failure đó. Boundary càng nhỏ thì feedback thường càng nhanh và dễ chẩn đoán; boundary càng rộng thì tín hiệu càng gần production nhưng setup, runtime và độ bất định cũng cao hơn.
2. Các test boundary chính
| Boundary | Tín hiệu cần chứng minh | Ví dụ | Điểm yếu chính |
|---|---|---|---|
| Unit | Business rule và state transition trong một phạm vi nhỏ, không cần infrastructure thật. | Pricing policy, validation, account state transition. | Không chứng minh wiring, serialization, SQL hoặc cấu hình framework. |
| Integration | Sự hợp tác với database, broker, filesystem hoặc framework thật. | Repository chạy với PostgreSQL; producer gửi vào broker. | Setup phức tạp hơn, chạy lâu hơn và cần quản lý dữ liệu test. |
| Component | Một service qua public boundary trong khi external dependencies được kiểm soát. | REST API với database container và payment stub. | Cần application runtime và không chứng minh toàn bộ journey liên service. |
| Contract | Consumer và provider còn tương thích về schema, field và semantics. | Order service kiểm tra contract của Payment API hoặc event schema. | Không chứng minh business journey hoặc production topology hoạt động. |
| End-to-end | Critical journey hoạt động qua deployment và các thành phần đã tích hợp. | Place order → pay → confirm. | Chậm, dễ flaky và khó xác định thành phần gây lỗi. |
3. Test pyramid là heuristic, không phải quota
Một test suite khỏe mạnh thường có nhiều test nhỏ, nhanh và deterministic ở đáy; ít integration/component test hơn; và chỉ giữ một số E2E cho critical journeys. Pyramid không quy định một tỷ lệ cố định như 70/20/10. Kiến trúc và risk profile mới quyết định tỷ lệ phù hợp.
Feedback flow mong muốn:
- Code change được kiểm tra ngay bằng test nhanh cho behavior cục bộ.
- Integration test xác nhận adapter, schema, transaction và semantics của infrastructure.
- Component và contract test xác nhận public boundary giữa các thành phần.
- Một số ít E2E xác nhận critical journey trên deployment gần production.
Nếu cùng một defect có thể được phát hiện ổn định ở boundary hẹp hơn, nên ưu tiên boundary đó. Failure xuất hiện càng sớm và càng gần nguyên nhân thì chi phí debug càng thấp.
4. Chọn boundary bằng câu hỏi rủi ro
- Chỉ là business rule thuần? Bắt đầu bằng unit test.
- Rủi ro nằm ở SQL, transaction, mapping hoặc framework configuration? Dùng integration test với technology thật.
- Cần chứng minh public API của một service? Dùng component test.
- Rủi ro là consumer/provider thay đổi không tương thích? Dùng contract test ở cả hai phía.
- Cần chứng minh deployment và critical journey? Dùng một E2E có ownership và dữ liệu ổn định.
- Failure liên quan concurrency hoặc duplicate request? Điều khiển scheduling/coordination và assert invariant cuối cùng, không chỉ assert từng lời gọi.
5. Determinism và testability
Test tốt nên hướng tới FIRST: Fast, Independent, Repeatable, Self-validating, Timely. Các nguồn nondeterminism như clock, random seed, locale, timezone, scheduler, thread coordination, network ngoài và shared mutable state phải có điểm kiểm soát.
- Inject
Clockthay vì gọi thời gian hệ thống trực tiếp trong domain logic. - Truyền random seed hoặc dùng generator deterministic khi kết quả cần tái hiện.
- Dùng fake scheduler hoặc virtual time cho logic theo thời gian.
- Dùng latch, barrier hoặc primitive đồng bộ thay cho
sleeptrong concurrency test. - Tách side effect ra sau port rõ ràng để business rule có thể được kiểm tra độc lập.
- Không để test phụ thuộc thứ tự chạy, dữ liệu dùng chung hoặc dịch vụ Internet bên ngoài.
6. Những failure mode phổ biến
- Ice-cream cone: quá nhiều E2E/UI test chậm và quá ít test ở boundary hẹp.
- Mock theater: test chỉ xác nhận setup hoặc interaction nội bộ, không chứng minh observable behavior.
- Coverage theater: tối ưu phần trăm coverage thay vì business risk và chất lượng assertion.
- Environment gap: substitute có semantics khác production ở nơi behavior thật là rủi ro.
- Ownership gap: test đỏ nhưng không có owner, quy trình triage hoặc thời hạn sửa.
- Hidden coupling: shared fixture, static state hoặc dữ liệu dùng lại khiến test phụ thuộc thứ tự và dễ flaky.
7. Câu trả lời phỏng vấn mẫu
Tiếng Việt
Tôi thiết kế test suite dựa trên rủi ro và tốc độ feedback. Business rule được bao phủ bằng unit test tập trung; rủi ro tích hợp được kiểm tra với infrastructure thật; component và contract test bảo vệ public boundary; E2E chỉ dành cho critical journey. Tỷ lệ giữa các lớp không cố định mà phụ thuộc kiến trúc, chi phí failure và mức độ cần fidelity với production.
English interview answer
I optimize the test suite for fast feedback and confidence. Most business rules are covered by focused unit tests, integration risks by tests against real infrastructure, and only critical user journeys by end-to-end tests. The exact ratio depends on architecture and risk.
8. Checklist tự đánh giá
- Tôi có thể giải thích vì sao một behavior thuộc unit, integration, component, contract hoặc E2E.
- Tôi biết failure nào mỗi lớp test có thể và không thể phát hiện.
- Tôi không dùng coverage percentage như bằng chứng duy nhất về chất lượng.
- Tôi kiểm soát được time, randomness, concurrency và external dependency trong test.
- Tôi có owner và quy trình xử lý cho flaky test.
- Tôi vẫn thiết kế canary, observability và rollback cho các failure test không thể loại bỏ.