Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 46 — Testing Strategy — Test Pyramid

Technical BA Masterclass Bài 46/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là Technical BA cho một dự án ví điện tử. Đội phát triển báo cáo: "Tụi em viết test đầy đủ rồi, coverage 85%." Nghe có vẻ yên tâm. Nhưng tới ngày go-live, hệ thống vỡ trận ngay ở luồng thanh toán liên ngân hàng — đúng cái luồng quan trọng nhất. Hóa ra 85% coverage đó nằm gần hết ở các hàm tiện ích nhỏ lẻ, còn luồng tích hợp end-to-end thì gần như không ai test. Đây là tình huống kinh điển khi đội ngũ "test nhiều" nhưng "test sai chỗ".

Nhiều người nghĩ testing là việc của QA và developer, BA không cần quan tâm. Sai lầm. Là Technical BA, bạn chính là người định nghĩa "thế nào là đúng" — bạn viết acceptance criteria, bạn mô tả các luồng nghiệp vụ, bạn biết đâu là rủi ro chí mạng với nghiệp vụ. Nếu bạn không hiểu chiến lược test, bạn sẽ không biết cách diễn đạt yêu cầu để đội có thể kiểm thử được, và cũng không biết chất vấn đội QA xem họ đang phủ đúng phần rủi ro hay chưa.

Test Pyramid (kim tự tháp kiểm thử) là mô hình tư duy giúp bạn trả lời câu hỏi cốt lõi: "Với một tính năng, ta nên test cái gì, ở tầng nào, bao nhiêu là đủ?" Hiểu nó, bạn sẽ viết spec có thể kiểm chứng được, ước lượng đúng công sức QA, và bảo vệ chất lượng sản phẩm ngay từ giai đoạn phân tích. Bài này không biến bạn thành QA, mà biến bạn thành BA biết "nói chuyện chất lượng" với đội kỹ thuật.

Khái niệm cốt lõi

Test Pyramid là gì?

Test Pyramid do Mike Cohn đề xuất, mô tả cách phân bổ các loại test theo hình kim tự tháp — nhiều ở dưới, ít dần khi lên cao:

          /\
         /UI\          ← Ít: chậm, đắt, dễ vỡ
        /----\
       /Integ.\        ← Vừa: kiểm thử tích hợp
      /--------\
     /   Unit   \      ← Nhiều: nhanh, rẻ, ổn định
    /____________\

Nguyên lý nền tảng: càng lên cao, test càng chậm, càng đắt để viết và bảo trì, càng dễ "vỡ" (flaky). Vì vậy phần lớn công sức kiểm thử nên dồn vào tầng đáy (unit test), thưa dần khi lên đỉnh (UI/end-to-end test). Một đội khỏe mạnh có thể có 2.000 unit test, 200 integration test, và chỉ 20 end-to-end test.

Ba tầng chính

Unit test (kiểm thử đơn vị) — kiểm tra một "đơn vị" nhỏ nhất của code, thường là một hàm hoặc một method, một cách độc lập. Ví dụ: hàm tính lãi suất tinhLai(goc, laiSuat, soNgay) có trả về đúng kết quả với nhiều bộ đầu vào không? Unit test chạy trong mili-giây, không cần database, không cần mạng. Đây là tầng đông đảo nhất.

Integration test (kiểm thử tích hợp) — kiểm tra nhiều thành phần phối hợp với nhau: code gọi database thật, gọi API của module khác, đọc message từ hàng đợi. Ví dụ: khi lưu một đơn hàng, dữ liệu có được ghi đúng vào bảng ordersorder_items không, ràng buộc khóa ngoại có chặt không. Chậm hơn unit test (tính bằng trăm mili-giây tới vài giây) vì phải dựng hạ tầng phụ thuộc.

UI / End-to-End (E2E) test — kiểm tra toàn bộ luồng từ góc nhìn người dùng thật, qua giao diện, xuyên suốt mọi tầng hệ thống. Ví dụ: mở trình duyệt, đăng nhập, thêm sản phẩm vào giỏ, thanh toán, nhận email xác nhận. Đây là test giống thực tế nhất nhưng cũng chậm nhất (vài giây tới vài phút mỗi ca), đắt nhất và dễ vỡ nhất — chỉ cần đổi một nút bấm là test gãy.

Các loại test bổ trợ mà BA hay gặp

Ngoài ba tầng chính, bạn sẽ nghe thêm vài thuật ngữ cần phân biệt:

  • Smoke test: bộ test tối thiểu chạy sau mỗi lần deploy, kiểm tra "hệ thống còn thở không" — đăng nhập được, trang chủ load được. Không sâu, chỉ để bắt lỗi chí mạng sớm.
  • Regression test: tập hợp test chạy lại để đảm bảo thay đổi mới không phá vỡ tính năng cũ. Là tài sản tích lũy dần theo thời gian.
  • Acceptance test (UAT): kiểm thử chấp nhận, thường do người dùng nghiệp vụ thực hiện, đối chiếu với acceptance criteria mà bạn — BA — đã viết.
  • Contract test: kiểm tra "hợp đồng" giữa hai service (ví dụ service A gọi API của service B) có khớp định dạng không. Cực kỳ hữu ích trong môi trường microservices, giúp giảm nhu cầu E2E test nặng nề.

Hai phản mẫu nổi tiếng: Ice Cream Cone và Hourglass

Nếu kim tự tháp bị lật ngược — nhiều E2E/UI test, ít unit test — ta có Ice Cream Cone (ốc quế). Đội nào rơi vào đây sẽ khổ sở: bộ test chạy hàng giờ, hay vỡ vặt, không ai dám tin kết quả, cuối cùng vô hiệu hóa luôn test. Hourglass (đồng hồ cát) là khi nhiều unit và nhiều E2E nhưng thiếu hẳn tầng integration ở giữa — lỗi tích hợp lọt lưới đúng như câu chuyện ví điện tử ở đầu bài. Vai trò của BA là giúp đội nhận ra mình đang ở hình dạng nào và phần rủi ro nghiệp vụ nào đang bị bỏ trống.

Tình huống thực tế

Tình huống 1 — Sàn TMĐT "ChợViệt" và bộ E2E phình to

ChợViệt là một sàn thương mại điện tử giả định ở TP.HCM, quy mô khoảng 40 lập trình viên. Trong hai năm đầu, đội ưu tiên ship nhanh, mọi tính năng đều được kiểm thử bằng cách viết kịch bản Selenium chạy qua giao diện. Tới năm thứ hai, bộ E2E lên tới 1.200 kịch bản, mỗi lần CI chạy mất 4 tiếng 20 phút, và khoảng 18% kịch bản "vỡ ngẫu nhiên" do thời gian chờ tải trang. Đội QA dành phần lớn thời gian "chạy lại cho nó xanh" thay vì tìm lỗi thật.

Khi BA mới về dự án, cô vẽ lại phân bố test và nhận ra đây là một Ice Cream Cone điển hình: chỉ 9% là unit test. Cô cùng tech lead chọn 5 luồng nghiệp vụ quan trọng nhất (đăng nhập, tìm kiếm, đặt hàng, thanh toán, hoàn tiền) để giữ lại ở tầng E2E, còn các kiểm tra chi tiết như "định dạng số tiền", "tính phí ship theo vùng" thì đẩy xuống unit và integration test. Sau ba tháng, bộ E2E còn 80 kịch bản cốt lõi, thời gian CI rút còn 35 phút, tỷ lệ vỡ ngẫu nhiên dưới 2%.

Bài học: Test ở giao diện trông "thật" và yên tâm, nhưng lạm dụng sẽ giết năng suất. BA là người hiểu luồng nào thật sự "đáng" test ở tầng E2E — chỉ những hành trình người dùng cốt lõi, không phải mọi quy tắc nhỏ.

Tình huống 2 — Ngân hàng số và lỗ hổng tầng tích hợp

Một ngân hàng số tại Việt Nam triển khai tính năng chuyển tiền nhanh NAPAS 24/7. Đội báo unit test coverage đạt 88%, mọi hàm tính phí, định dạng số tài khoản, kiểm tra hạn mức đều được test kỹ. Nhưng ngay tuần đầu vận hành, xảy ra sự cố: khi NAPAS trả về mã lỗi tạm thời (timeout), hệ thống trừ tiền tài khoản người gửi nhưng không hoàn lại khi giao dịch thất bại — vì luồng xử lý phản hồi từ NAPAS chưa từng được test ở tầng tích hợp.

Đây là Hourglass: unit nhiều, E2E có vài cái cho "happy path", nhưng tầng integration mô phỏng các phản hồi bất thường từ đối tác thì trống. BA của dự án sau đó bổ sung vào spec một mục bắt buộc: "Với mỗi tích hợp ngoài, liệt kê các mã phản hồi có thể xảy ra (thành công, timeout, từ chối, lỗi hệ thống) và hành vi kỳ vọng tương ứng." Đội QA dùng công cụ giả lập (mock NAPAS) để viết integration test cho từng kịch bản. Đặc biệt với nghiệp vụ tiền bạc, kịch bản idempotency — gọi lại hai lần không được trừ tiền hai lần — trở thành test bắt buộc.

Bài học: Coverage cao ở unit test không bảo vệ bạn khỏi lỗi tích hợp. Với nghiệp vụ tài chính, phần rủi ro chí mạng nằm ở ranh giới giữa các hệ thống — và đó chính là nơi BA phải kéo sự chú ý của đội vào integration test, đặc biệt cho các luồng lỗi.

Tình huống 3 — Startup logistics và "test pyramid trên giấy"

Một startup logistics ở Hà Nội, đội 12 người, áp dụng đúng tỷ lệ kim tự tháp về mặt số lượng: 1.500 unit, 150 integration, 20 E2E. Trông rất đẹp. Nhưng khi có sự cố tính sai cước vận chuyển liên tỉnh, điều tra ra mới thấy phần lớn unit test chỉ test những đoạn code "dễ test" như getter/setter, còn logic tính cước phức tạp — nơi có nhiều quy tắc nghiệp vụ chồng chéo theo vùng, trọng lượng, loại hàng — lại gần như không được test với các trường hợp biên.

BA ngồi lại cùng đội, không nhìn vào con số coverage nữa mà nhìn vào rủi ro nghiệp vụ. Cô lập một bảng đối chiếu: với mỗi business rule trong spec tính cước, có ít nhất một test tương ứng không? Kết quả phát hiện 14 quy tắc chưa có test nào. Đội bổ sung test cho đúng những chỗ đó. Hình dạng kim tự tháp không đổi nhiều, nhưng chất lượng phủ test ở đúng chỗ quan trọng tăng vọt.

Bài học: Hình dạng kim tự tháp đúng chưa đủ. Quan trọng hơn là test có phủ đúng phần logic nghiệp vụ rủi ro cao hay không. BA là cầu nối duy nhất biết rule nào "đắt giá" nếu sai.

Hướng dẫn từng bước

Đây là quy trình thực tế để một Technical BA đóng góp vào chiến lược test cho mỗi tính năng:

Bước 1 — Viết acceptance criteria có thể kiểm thử được. Mỗi tiêu chí chấp nhận phải cụ thể, đo được. Thay vì "hệ thống xử lý thanh toán", hãy viết theo dạng Given-When-Then: "Cho người dùng có số dư 500.000đ, khi thanh toán đơn 600.000đ, thì hệ thống từ chối và hiển thị thông báo 'Số dư không đủ'." Mỗi tiêu chí như vậy là một test case tiềm năng.

Bước 2 — Phân loại rủi ro của tính năng. Tự hỏi: nếu phần này sai thì hậu quả thế nào? Mất tiền? Sai dữ liệu khách hàng? Chỉ là lỗi hiển thị nhỏ? Phần rủi ro cao cần test dày và sâu hơn ở nhiều tầng.

Bước 3 — Ánh xạ mỗi tiêu chí vào tầng test phù hợp. Quy tắc tính toán thuần túy → unit test. Tương tác với database hoặc service khác → integration test. Hành trình người dùng đầu-cuối quan trọng → một (và chỉ một) E2E test. Đừng đẩy mọi thứ lên E2E.

Bước 4 — Liệt kê trường hợp biên và luồng lỗi. Với mỗi tích hợp ngoài, ghi rõ các phản hồi có thể (thành công, timeout, từ chối, lỗi). Với mỗi input, ghi rõ giá trị biên (0, âm, rỗng, vượt hạn mức, ký tự đặc biệt). Đây thường là phần dev hay quên, và là nơi BA tạo giá trị lớn nhất.

Bước 5 — Cùng QA/dev rà soát phân bố test. Trong buổi refinement, hỏi: "Phần nào ta test ở unit, phần nào ở integration, có cần E2E không?" Đảm bảo không rơi vào Ice Cream Cone hay Hourglass.

Bước 6 — Định nghĩa Definition of Done gắn với test. Một story chỉ "done" khi các test tương ứng (theo đúng tầng) đã pass. Gắn điều này vào quy ước chung của đội.

Lỗi thường gặp & mẹo

Lỗi 1 — Thần thánh hóa con số coverage. "Coverage 80%" không nói gì về việc bạn có test đúng phần quan trọng hay không. Mẹo: thay vì hỏi "coverage bao nhiêu", hãy hỏi "mỗi business rule rủi ro cao có ít nhất một test chưa?".

Lỗi 2 — Đẩy mọi thứ lên E2E vì nó "giống thật". Đây là con đường nhanh nhất tới Ice Cream Cone. Mẹo: chỉ giữ E2E cho các hành trình người dùng cốt lõi (thường dưới 10-15 luồng cho cả sản phẩm), phần còn lại đẩy xuống thấp.

Lỗi 3 — Quên test luồng lỗi. Đội thường chỉ test "happy path". Mẹo: là BA, với mỗi luồng thành công, luôn đặt câu hỏi "còn nếu thất bại thì sao?" và viết nó vào acceptance criteria.

Lỗi 4 — Viết acceptance criteria mơ hồ. "Hệ thống phải nhanh", "phải thân thiện" — không test được. Mẹo: dùng định dạng Given-When-Then với số liệu cụ thể.

Lỗi 5 — Bỏ qua flaky test. Test vỡ ngẫu nhiên làm đội mất niềm tin vào cả bộ test. Mẹo: coi một test flaky cũng nghiêm trọng như một bug, phải sửa hoặc bỏ, không để "treo".

Lỗi 6 — Nhầm test pyramid là quy tắc cứng về số lượng. Tỷ lệ chỉ là gợi ý, không phải luật. Mẹo: ưu tiên giá trị và rủi ro, dùng kim tự tháp như la bàn chứ không phải thước đo cứng.

Bài tập thực hành

Bài 1 — Phân tầng test. Cho tính năng "đăng ký tài khoản" với các yêu cầu: (a) email phải đúng định dạng, (b) mật khẩu tối thiểu 8 ký tự có cả chữ và số, (c) email chưa tồn tại trong database, (d) gửi email kích hoạt qua dịch vụ bên thứ ba, (e) người dùng nhấn link kích hoạt và đăng nhập thành công. Hãy gán mỗi yêu cầu vào tầng test phù hợp (unit / integration / E2E) và giải thích lý do.

Bài 2 — Viết acceptance criteria. Lấy tính năng "rút tiền từ ví điện tử". Viết ít nhất 5 acceptance criteria theo định dạng Given-When-Then, trong đó bắt buộc có ít nhất 2 luồng lỗi (ví dụ: số dư không đủ, dịch vụ ngân hàng timeout).

Bài 3 — Chẩn đoán hình dạng. Một đội báo cáo: 60 unit test, 30 integration test, 200 E2E test, CI chạy 3 tiếng, hay vỡ. Hãy chỉ ra đội đang ở phản mẫu nào, hậu quả, và đề xuất 3 hành động cụ thể để cải thiện.

Bài 4 — Rà soát rủi ro. Chọn một tính năng trong dự án thật của bạn. Liệt kê tất cả business rule, rồi đánh dấu rule nào "nếu sai thì hậu quả nghiêm trọng". Kiểm tra xem mỗi rule đó đã có test tương ứng chưa.

Tóm tắt

Test Pyramid là mô hình tư duy phân bổ test theo ba tầng: unit (nhiều, nhanh, rẻ) ở đáy, integration (vừa) ở giữa, UI/E2E (ít, chậm, đắt) ở đỉnh. Càng lên cao, test càng chậm, đắt và dễ vỡ — nên đừng dồn công sức lên đỉnh. Hai phản mẫu cần tránh là Ice Cream Cone (lật ngược kim tự tháp) và Hourglass (rỗng tầng tích hợp).

Là Technical BA, vai trò của bạn không phải viết test, mà là: viết acceptance criteria có thể kiểm thử được theo dạng Given-When-Then, phân loại rủi ro nghiệp vụ, ánh xạ yêu cầu vào đúng tầng test, và đặc biệt là không quên các luồng lỗi cùng trường hợp biên. Ba câu chuyện — ChợViệt với E2E phình to, ngân hàng số với lỗ hổng tích hợp, và startup logistics với "kim tự tháp trên giấy" — đều cho thấy một bài học chung: con số coverage hay hình dạng kim tự tháp đẹp không đủ; điều quan trọng là test có phủ đúng phần rủi ro nghiệp vụ hay không. Và đó chính là chỗ BA tạo ra giá trị không ai thay thế được.