Product Management
Đăng nhập
ESC

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

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

Bài 19 — Test Coverage — Code vs Requirement

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

Có một câu chuyện gần như trở thành "kinh điển" trong giới QA: một team báo cáo với ban lãnh đạo rằng họ đã đạt 95% test coverage. Con số đẹp, sếp gật gù, dự án được duyệt release. Ba ngày sau khi lên production, một luồng thanh toán quan trọng bị lỗi và không có test case nào chạm tới nó. Câu hỏi đau đớn được đặt ra trong buổi post-mortem: "Nếu coverage tới 95% thì tại sao lỗi này lọt lưới?"

Câu trả lời nằm ở chỗ: 95% đó là coverage của cái gì? Nếu là code coverage, nó chỉ nói rằng 95% dòng code đã được thực thi trong lúc test — chứ không nói rằng 95% yêu cầu nghiệp vụ đã được kiểm chứng. Đây là hai thế giới khác nhau, và việc nhầm lẫn giữa chúng là một trong những sai lầm phổ biến nhất mà một QA Lead phải chịu trách nhiệm.

Bài này sẽ giúp bạn phân biệt rạch ròi Code CoverageRequirement Coverage — hiểu bản chất, biết đọc con số cho đúng, và quan trọng nhất là biết cách dùng cả hai như một cặp chỉ số bổ trợ nhau để ra quyết định release có căn cứ, thay vì tự ru ngủ bằng một con số phần trăm hào nhoáng.

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

Coverage (độ bao phủ) trả lời cho câu hỏi: "Chúng ta đã kiểm thử tới đâu?" Nhưng "tới đâu" có thể đo theo hai trục hoàn toàn khác nhau.

Code Coverage — bao phủ theo mã nguồn

Code coverage đo tỷ lệ phần trăm mã nguồn được thực thi khi bộ test chạy. Nó là chỉ số white-box — bạn cần nhìn vào bên trong code để đo. Công cụ đo (JaCoCo cho Java, Istanbul/nyc cho JavaScript, Coverage.py cho Python, dotCover cho .NET) sẽ gắn instrument vào code, chạy test, và đếm xem những phần nào được "chạm" tới.

Có nhiều cấp độ code coverage, từ dễ đạt tới khó đạt:

  • Line coverage (bao phủ dòng lệnh): phần trăm dòng code được thực thi ít nhất một lần. Đây là chỉ số dễ hiểu nhất nhưng cũng "lỏng" nhất. Một dòng được chạy không có nghĩa là nó được chạy với đúng dữ liệu để lộ ra lỗi.
  • Statement coverage (bao phủ câu lệnh): gần giống line coverage nhưng đếm theo câu lệnh logic (một dòng có thể chứa nhiều câu lệnh).
  • Branch coverage (bao phủ nhánh): phần trăm các nhánh rẽ (if/else, switch, toán tử ba ngôi) được đi qua. Đây là cấp độ nghiêm túc hơn nhiều. Ví dụ với if (a && b), để đạt branch coverage bạn phải có test cho cả trường hợp điều kiện đúng lẫn sai. Line coverage có thể đạt 100% mà branch coverage chỉ 50% — vì bạn chỉ chạy nhánh true mà quên nhánh false.
  • Condition coverage (bao phủ điều kiện): đi sâu hơn branch, yêu cầu mỗi điều kiện con trong một biểu thức logic phải được test với cả giá trị true lẫn false. Với if (a && b), bạn cần test a cả đúng/sai và b cả đúng/sai.
  • Path coverage (bao phủ đường đi): khắt khe nhất, yêu cầu mọi tổ hợp đường đi có thể qua hàm đều được test. Trên thực tế gần như bất khả thi với hàm phức tạp vì số đường đi bùng nổ tổ hợp.
Điểm mấu chốt cần nhớ: code coverage cao chỉ chứng minh code đã được chạy, không chứng minh nó đã được kiểm chứng đúng. Một test không có assertion nào vẫn có thể "chạy qua" code và làm coverage tăng vọt — nhưng nó chẳng phát hiện được lỗi nào. Đây là cái bẫy lớn nhất của việc chạy theo con số.

Requirement Coverage — bao phủ theo yêu cầu

Requirement coverage đo tỷ lệ phần trăm yêu cầu (nghiệp vụ, chức năng, phi chức năng) đã có test case kiểm chứng. Đây là chỉ số black-box — bạn không cần nhìn vào code, mà nhìn vào tài liệu đặc tả, user story, acceptance criteria.

Công thức đơn giản:

> Requirement Coverage = (Số yêu cầu có ít nhất một test case mapping tới) / (Tổng số yêu cầu) × 100%

Công cụ chính để quản lý requirement coverage là Traceability Matrix (ma trận truy vết) — một bảng ánh xạ hai chiều giữa yêu cầu và test case. Mỗi requirement phải nối được tới ít nhất một test case; ngược lại mỗi test case nên truy vết được về một requirement (nếu không, có thể bạn đang test thừa những thứ không ai cần).

Requirement coverage trả lời câu hỏi mà lãnh đạo và khách hàng thực sự quan tâm: "Những gì chúng ta hứa với khách hàng, chúng ta đã kiểm chứng hết chưa?" Nó không quan tâm code chạy bao nhiêu dòng, nó quan tâm hành vi mong đợi có được đảm bảo hay không.

Vì sao cần cả hai

Hãy hình dung bằng một bảng đối chiếu tư duy:

Tiêu chíCode CoverageRequirement Coverage
Góc nhìnWhite-box (bên trong)Black-box (bên ngoài)
Trả lời câu hỏiCode nào đã chạy?Yêu cầu nào đã kiểm chứng?
Điểm mùKhông thấy yêu cầu bị thiếu codeKhông thấy code thừa/dead code
Đối tượng quan tâmDeveloper, SDETPO, khách hàng, QA Lead
Đo bằngCông cụ tự động (JaCoCo...)Traceability Matrix
Điểm mù chí mạng của code coverage: nếu một yêu cầu chưa được ai code, thì nó cũng chẳng có dòng code nào để đo — và code coverage sẽ không bao giờ báo động về sự thiếu vắng đó. Bạn có thể đạt 100% code coverage trên một sản phẩm mà vẫn thiếu hẳn một tính năng khách hàng yêu cầu. Chỉ requirement coverage mới lộ ra được "khoảng trống" này.

Ngược lại, requirement coverage không thấy được dead code, các nhánh xử lý lỗi ngầm, hay logic phòng thủ mà đặc tả không nhắc tới. Đó là lúc code coverage phát huy tác dụng.

Kết luận nghề nghiệp: hai chỉ số này không thay thế nhau, chúng bù đắp điểm mù của nhau. Một QA Lead giỏi luôn nhìn cả hai.

Tình huống thực tế

Tình huống 1 — Ngân hàng số VPBank NEO và cái bẫy 92% code coverage

Một đội phát triển tính năng "chuyển tiền nhanh Napas 24/7" cho ứng dụng ngân hàng số tại Việt Nam đặt cổng chất lượng (quality gate) yêu cầu code coverage tối thiểu 90% mới cho merge. Team đạt 92%, ai cũng yên tâm.

Nhưng khi UAT, tester nghiệp vụ phát hiện: yêu cầu "chặn chuyển tiền khi tài khoản người nhận nằm trong danh sách nghi ngờ gian lận (blacklist)" hoàn toàn chưa được cài đặt. Developer đọc user story nhưng bỏ sót acceptance criteria số 7. Vì không có code cho tính năng này, nên nó không kéo code coverage xuống chút nào — 92% vẫn là 92%.

Diễn giải: Code coverage đo trên phần code đã viết. Yêu cầu bị bỏ quên không tồn tại dưới dạng code, nên "vô hình" với công cụ đo. Chỉ khi team dựng traceability matrix, đối chiếu 7 acceptance criteria với danh sách test case, họ mới thấy dòng "AC-7: chặn blacklist" trống trơn cột test case.

Bài học: Với sản phẩm tài chính, requirement coverage phải là cổng gác đầu tiên, code coverage chỉ là cổng thứ hai. Nếu chỉ dựa vào con số 92%, lỗ hổng tuân thủ chống gian lận đã lọt ra thị trường.

Tình huống 2 — Startup e-commerce Tiki-style và những test "rỗng ruột"

Một startup thương mại điện tử ở TP.HCM đặt KPI cho team QA: "đạt 80% code coverage trước cuối quý". Áp lực KPI khiến vài kỹ sư viết test theo kiểu chạy qua hàm cho tăng số, nhưng assertion sơ sài — có test còn không kiểm tra kết quả trả về, chỉ gọi hàm rồi kết thúc.

Coverage report cuối quý: 81%, đạt KPI, cả team ăn mừng. Nhưng số bug production trong quý đó không hề giảm. Khi review lại, QA Lead phát hiện khoảng 30% test case là "coverage rỗng" — chạy code nhưng không assert gì có ý nghĩa. Hàm tính phí vận chuyển được "cover" nhưng không có test nào kiểm tra công thức tính đúng hay sai.

Diễn giải: Coverage là điều kiện cần nhưng không đủ. Một dòng code được thực thi (đóng góp vào %) hoàn toàn khác với dòng code đó được kiểm chứng đúng. Khi coverage trở thành mục tiêu tự thân, nó ngừng là thước đo tốt — đúng như định luật Goodhart: "Khi một thước đo trở thành mục tiêu, nó thôi là một thước đo tốt."

Bài học: Đừng bao giờ đặt KPI coverage một mình. Hãy kết hợp với mutation testing (đo chất lượng test bằng cách cố tình gài lỗi vào code xem test có bắt được không) hoặc review chất lượng assertion. Con số coverage cần được đọc kèm ngữ cảnh, không phải thờ phụng.

Tình huống 3 — Công ty gia công (outsourcing) và traceability matrix cứu hợp đồng

Một công ty phần mềm gia công tại Đà Nẵng nhận dự án cho khách hàng Nhật với đặc tả gồm 240 requirement được đánh số rõ ràng. Khách hàng yêu cầu bàn giao kèm báo cáo requirement coverage 100% — mỗi requirement phải truy vết được tới test case và kết quả pass.

Ban đầu team chỉ quen theo dõi code coverage. Khi khách hàng hỏi "requirement REQ-118 về xử lý múi giờ được test bằng case nào?", team lúng túng vì không có ánh xạ. Họ buộc phải dựng traceability matrix, và phát hiện 18 requirement chưa có test case nào, trong đó 5 cái liên quan xử lý ngày giờ theo chuẩn Nhật (một rủi ro thực sự).

Diễn giải: Requirement coverage không chỉ là chỉ số kỹ thuật, mà còn là ngôn ngữ giao tiếp với khách hàng và bằng chứng hợp đồng. Trong môi trường outsourcing hay dự án có kiểm toán, khả năng truy vết từng yêu cầu tới test là điều bắt buộc để chứng minh chất lượng đã cam kết.

Bài học: Với dự án theo hợp đồng chặt chẽ, hãy xây traceability matrix ngay từ đầu song song với việc viết đặc tả, đừng để tới lúc bàn giao mới dựng ngược lại.

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

Đây là quy trình thiết lập một hệ thống coverage lành mạnh cho team của bạn:

Bước 1 — Xác định "nguồn chân lý" cho requirement. Chốt danh sách requirement/user story/acceptance criteria có đánh mã (REQ-001, US-042...). Nếu đặc tả mơ hồ, coverage sẽ vô nghĩa vì bạn không biết đang đo tới cái gì.

Bước 2 — Dựng Traceability Matrix. Tạo bảng với các cột: Mã requirement | Mô tả | Test case ID mapping | Trạng thái (Pass/Fail/Chưa test) | Mức ưu tiên/rủi ro. Có thể làm trong Jira + Xray/Zephyr, TestRail, hoặc đơn giản là Google Sheet với dự án nhỏ.

Bước 3 — Tính requirement coverage. Đếm số requirement có ít nhất một test case và đang ở trạng thái đã test. Ưu tiên soi những requirement rủi ro cao mà cột test case còn trống — đó là lỗ hổng nguy hiểm nhất.

Bước 4 — Bật đo code coverage trong CI. Tích hợp công cụ (JaCoCo, Istanbul...) vào pipeline. Đặt ngưỡng hợp lý theo cấp độ branch coverage (nghiêm túc hơn line coverage), không quá thấp để mất tác dụng, không đặt 100% một cách cực đoan gây phản tác dụng.

Bước 5 — Đọc hai chỉ số cùng nhau. Với mỗi module, hỏi: Requirement coverage có đủ chưa? Trong phần code đã viết, branch coverage có tốt không? Sự chênh lệch giữa hai con số thường chỉ ra vấn đề: requirement cao mà code coverage thấp nghĩa là có logic ẩn chưa được test; ngược lại code cao mà requirement thấp nghĩa là có thể đang test những thứ không ai cần.

Bước 6 — Đưa vào quality gate và báo cáo. Đặt cổng release dựa trên cả hai. Ví dụ: 100% requirement rủi ro cao được test + branch coverage ≥ 80% trên code mới. Báo cáo cho lãnh đạo bằng requirement coverage (ngôn ngữ nghiệp vụ), báo cho dev bằng code coverage (ngôn ngữ kỹ thuật).

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

Lỗi 1 — Coi code coverage là bằng chứng chất lượng. Coverage cao không đồng nghĩa ít bug. Mẹo: dùng mutation testing (PIT cho Java, Stryker cho JS) để kiểm tra xem test có thực sự "bắt lỗi" hay chỉ chạy qua code.

Lỗi 2 — Đặt mục tiêu 100% code coverage. Chi phí để đi từ 85% lên 100% thường lớn hơn nhiều so với giá trị nhận được, và dễ đẻ ra những test giả tạo. Mẹo: tập trung branch coverage cao ở phần code quan trọng (thanh toán, bảo mật), chấp nhận thấp hơn ở phần rủi ro thấp.

Lỗi 3 — Chỉ đo line coverage, bỏ qua branch coverage. Line 100% có thể che giấu branch 50%. Mẹo: luôn cấu hình báo cáo hiển thị branch coverage như chỉ số chính.

Lỗi 4 — Bỏ quên requirement coverage. Đây là điểm mù nguy hiểm nhất vì nó không tự báo động. Mẹo: biến traceability matrix thành tài liệu sống, cập nhật mỗi sprint.

Lỗi 5 — Không tính coverage cho yêu cầu phi chức năng. Performance, security, accessibility cũng là requirement. Mẹo: đưa cả requirement phi chức năng vào ma trận, đừng chỉ đếm chức năng.

Mẹo vàng: Hãy nhớ khẩu quyết — "Requirement coverage cho bạn biết đã test đủ chưa; code coverage cho bạn biết đã test kỹ chưa." Thiếu một trong hai đều là điểm mù.

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

  • Dựng ma trận truy vết mini: Chọn một tính năng bạn đang làm (ví dụ "đăng nhập"). Liệt kê 5–7 acceptance criteria, tạo bảng traceability matrix mapping từng criteria tới test case. Tính requirement coverage. Có criteria nào chưa có test không?
  • Phân biệt line vs branch: Viết một hàm có if (a && b). Viết một test chỉ truyền a=true, b=true. Chạy công cụ đo coverage và quan sát: line coverage bao nhiêu, branch coverage bao nhiêu? Giải thích khoảng chênh.
  • Săn "test rỗng ruột": Lấy 5 test case bất kỳ trong dự án. Kiểm tra từng cái có assertion kiểm chứng kết quả thực sự không, hay chỉ gọi hàm. Ghi lại tỷ lệ test "rỗng".
  • Đọc chênh lệch hai chỉ số: Với một module, ghi lại requirement coverage và branch coverage. Nếu hai số chênh nhau lớn, đưa ra giả thuyết nguyên nhân và cách khắc phục.

Tóm tắt

  • Code coverage đo phần trăm mã nguồn được thực thi khi test chạy — góc nhìn white-box. Các cấp độ từ dễ tới khó: line → statement → branch → condition → path. Branch coverage nghiêm túc hơn line và nên là chỉ số chính.
  • Requirement coverage đo phần trăm yêu cầu đã được test case kiểm chứng — góc nhìn black-box, quản lý bằng traceability matrix.
  • Hai chỉ số bù đắp điểm mù cho nhau: code coverage không thấy yêu cầu bị bỏ quên; requirement coverage không thấy dead code và logic ẩn.
  • Coverage cao là điều kiện cần chứ không đủ. Test "rỗng ruột" vẫn làm tăng con số mà không bắt được lỗi — hãy kết hợp mutation testing.
  • Đừng thờ phụng con số phần trăm. Đặt quality gate dựa trên cả hai, ưu tiên 100% requirement rủi ro cao và branch coverage tốt ở code quan trọng.
  • Câu thần chú để mang theo: requirement coverage — đã test đủ chưa; code coverage — đã test kỹ chưa.
Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi