Daniel Nguyen
← Tất cả dự án

Coverage check trong CI không thể bị pha loãng

Tôi thay tỉ lệ coverage toàn cục bằng một bước kiểm tra trong CI, làm fail pull request khi có bất kỳ dòng hay nhánh thay đổi nào chưa được test.

TTMI
Mọi nhánh code mớiDòng và nhánh chưa test sẽ làm fail PR
Mảng
Nền tảng ERP bán lẻ
Thời gian
2025–2026
Vai trò
Thiết kế và triển khai
  • Python
  • pytest
  • coverage.py
  • GitHub Actions
  • Git

Các phần ghép với nhau thế nào

Coverage check trong CI không thể bị pha loãngThe coverage check joins the pull request's changed statements with line and branch data and fails on any untested change.Developerngười dùngPull requestdịch vụTest suiteworkerLine + branch reportkho dữ liệuDiff analyzerdịch vụStatement wideningdịch vụCoverage checkkiểm tra, cảnh báoPR statusngười dùngopens / pushesrun with branch dataproducesdiff vs merge basechanged linescovered lines+brancheschanged statementspass / fail + gaps
The coverage check joins the pull request's changed statements with line and branch data and fails on any untested change.
  • Người dùng
  • Dịch vụ
  • Worker
  • Kho dữ liệu
  • Kiểm tra, cảnh báo

Vấn đề

Hệ thống ERP bán lẻ của chúng tôi là một codebase Python/Django lớn, phát triển nhanh và có nhiều người cùng đóng góp. Test coverage được kiểm soát bằng một tỉ lệ phần trăm toàn cục, kèm nhiều loại trừ rộng. Chỉ cần con số vẫn trên ngưỡng là pull request được qua.

Vấn đề là tỉ lệ toàn cục đo cả repository và bỏ qua chính thay đổi đang được review. Khi codebase đã có hàng nghìn dòng được test, một pull request có thể thêm vài trăm dòng không có test mà con số gần như không đổi. Các test sẵn có làm loãng đi khoảng trống mới. Bước kiểm tra pass đúng vào lúc lẽ ra phải fail, và càng yếu đi khi codebase càng lớn.

Tôi thiết kế và triển khai một coverage check chỉ nhìn vào những gì pull request thay đổi, rồi gắn nó vào quy trình pull request.

Tôi đã làm gì

Kiểm tra phần diff thay vì tổng thể

Coverage check tính các dòng mà pull request thay đổi bằng cách diff với merge base, rồi yêu cầu tất cả những dòng đó phải được test bao phủ. Code cũ không bị xét, nên không ai phải sửa cả lịch sử nhiều năm chỉ để ship một thay đổi nhỏ. Cái giá là sự vướng víu có thật: một pull request lớn giờ cần test đầy đủ cho mọi thứ nó chạm vào. Nâng ngưỡng toàn cục thì dễ áp dụng hơn ngay ngày đầu, nhưng luôn có thể bị lách bằng số lượng.

Bắt buộc có dữ liệu nhánh, và từ chối report thiếu nó

Chỉ đo theo dòng sẽ bỏ sót một trường hợp phổ biến. Một câu lệnh if chạy dòng của nó mỗi lần được đánh giá, kể cả khi chỉ một nhánh từng được đi qua. Vì vậy coverage check yêu cầu đo theo nhánh và fail ngay nếu coverage report được tạo ra mà không có dữ liệu nhánh. Điều này bịt lỗ hổng khi một điều kiện trông như đã được test chỉ vì dòng của nó từng chạy một lần. Cái giá là test chạy chậm hơn một chút và lúc đầu có nhiều lần fail cần giải thích hơn.

Mở rộng dòng thay đổi thành cả câu lệnh

Một dòng thay đổi thường thuộc về một câu lệnh dài, được ngắt xuống nhiều dòng. Nếu chỉ dòng đầu được tính là thay đổi, phần tiếp theo chưa test có thể ẩn bên dưới. Coverage check dùng cây cú pháp của Python để mở rộng mỗi dòng thay đổi thành toàn bộ câu lệnh chứa nó. Việc này làm bản thân coverage check phức tạp hơn một chút, nhưng loại bỏ một cách lách lặng lẽ.

Không có lối thoát cho code ứng dụng

Thứ duy nhất được loại trừ khỏi coverage check là chính nó. Nếu ai muốn loại trừ code ứng dụng, phần loại trừ đó phải nằm trong diff để reviewer thấy và đặt câu hỏi. Cách này chặt hơn thiết lập cũ với nhiều loại trừ rộng, và đôi khi buộc phải viết test cho đoạn code có vẻ quá đơn giản.

Thông báo lỗi chỉ rõ cần viết test gì

Một bước kiểm tra chặt mà báo lỗi mơ hồ sẽ khiến mọi người tìm cách chống lại nó. Khi fail, coverage check liệt kê từng vị trí chưa được bao phủ và từng đích nhánh chưa bao giờ được đi tới, để tác giả biết chính xác test nào còn thiếu. Làm phần thông báo này cho đúng tốn thời gian gần bằng phần logic chính.

Kết quả

Coverage check chạy trong CI cho các pull request chạm vào vùng ứng dụng lớn nhất của codebase. Code mới trong vùng đó không còn có thể kéo coverage thực tế của thay đổi đang review xuống, vì dòng và nhánh thay đổi chưa có test sẽ làm fail pull request.

Cách review cũng đổi khác. Thay vì hỏi coverage còn trên con số nào đó không, reviewer và tác giả bàn về việc nhánh nào trong chính thay đổi này chưa có test.

Nếu làm lại

Nếu làm lại, tôi sẽ cho nó chạy ở chế độ chỉ cảnh báo trong vài tuần trước khi bắt buộc, để team quen với output và góp ý trước khi nó chặn merge. Tôi cũng sẽ mở rộng nó ra phần còn lại của codebase sớm hơn, vì vùng nằm ngoài coverage check chính là nơi coverage vẫn có thể tụt.