Khi AI viết kiểm thử nhanh và nhiều, câu hỏi không còn là "có kiểm thử chưa" mà là "kiểm thử có bắt được lỗi không". Độ phủ kiểm thử trả lời câu hỏi thứ nhất khá tốt và câu hỏi thứ hai rất kém. Kiểm thử đột biến, kỹ thuật cố tình gài lỗi vào mã rồi xem kiểm thử có phát hiện không, trả lời thẳng câu hỏi thứ hai.
Bài này giải thích các loại độ phủ và giới hạn của chúng, bàn chuyện bao nhiêu phần trăm là đủ, rồi đi vào kiểm thử đột biến: nguyên lý, công cụ theo ngôn ngữ, một ví dụ giả định, cách dùng AI để xử lý danh sách đột biến còn sống, và cách đưa vào quy trình mà không làm mọi thứ chậm lại.
Độ phủ đo gì và không đo gì
Công cụ đo độ phủ ghi lại dòng nào, nhánh nào của mã đã được chạy qua khi bộ kiểm thử chạy. Kết quả là con số phần trăm và báo cáo tô màu từng dòng. Báo cáo đó rất có ích để thấy phần mã chưa ai đụng tới: một nhánh xử lý lỗi chưa từng chạy, một hàm chưa có kiểm thử nào.
Điều độ phủ không đo được là kết quả có được kiểm hay không. Một kiểm thử gọi hàm rồi không khẳng định gì vẫn làm mọi dòng của hàm đó được tính là đã phủ. Một kiểm thử khẳng định lỏng như "kết quả không rỗng" cũng vậy. Với kiểm thử do AI viết, khoảng cách giữa "đã chạy qua" và "đã kiểm" có thể rất lớn, vì AI dễ đạt độ phủ cao bằng những ca chạy nhiều mà kiểm ít.
Vì thế, đọc báo cáo độ phủ theo chiều ngược: phần chưa phủ chắc chắn là chưa được kiểm, còn phần đã phủ thì mới chỉ là có thể đã được kiểm. Con số cao không chứng minh gì; con số thấp ở chỗ quan trọng thì chứng minh có vấn đề.
Các loại độ phủ thường gặp
Công cụ độ phủ của từng ngôn ngữ hỗ trợ các loại khác nhau; kiểm tài liệu của công cụ bạn dùng. Bảng dưới là ý nghĩa chung:
| Loại | Đếm cái gì | Bỏ sót điều gì |
|---|---|---|
| Độ phủ dòng | Dòng mã đã chạy ít nhất một lần | Một dòng có điều kiện kép chỉ cần chạy một vế là tính đã phủ |
| Độ phủ nhánh | Mỗi nhánh đúng và sai của từng điều kiện đã chạy chưa | Không biết kết quả của nhánh có được kiểm không |
| Độ phủ hàm | Hàm đã được gọi ít nhất một lần | Rất thô, hàm lớn gọi một lần vẫn tính là đủ |
| Độ phủ điều kiện | Từng vế trong điều kiện phức hợp đã nhận cả giá trị đúng và sai chưa | Ít công cụ hỗ trợ đầy đủ, báo cáo khó đọc |
Nếu chỉ chọn một, độ phủ nhánh có ích hơn độ phủ dòng. Nhiều lỗi của mã AI viết nằm ở nhánh ít khi chạy: nhánh xử lý lỗi, nhánh danh sách rỗng, nhánh người dùng không có quyền. Độ phủ dòng có thể đạt rất cao mà những nhánh này chưa hề chạy. Khi nhờ AI đọc báo cáo độ phủ, yêu cầu nó liệt kê riêng các nhánh chưa chạy trong phần mã quan trọng, kèm câu hỏi nhánh đó xảy ra trong tình huống thật nào.
Bao nhiêu phần trăm là đủ
Không có con số đúng cho mọi dự án, và đặt một ngưỡng cứng thường gây hậu quả ngược. Khi nhóm bị yêu cầu đạt một mức phần trăm nào đó, cách nhanh nhất là nhờ AI viết thêm kiểm thử cho những dòng dễ phủ nhất, thường là mã ít rủi ro nhất. Con số đẹp lên, rủi ro không giảm.
Cách dùng hợp lý hơn có ba ý. Một, theo dõi chiều hướng thay vì mức tuyệt đối: không để độ phủ của nhánh chính giảm sau mỗi lần gộp mã. Hai, nhìn độ phủ theo vùng: phần tính tiền, phân quyền, xử lý dữ liệu khách cần gần như đủ; phần giao diện thử nghiệm, kịch bản dùng một lần thì thấp cũng không sao. Ba, khi rà một thay đổi, xem độ phủ của riêng phần mã mới; nhiều công cụ hiển thị được điều này ngay trong yêu cầu gộp mã.
Sau tất cả, độ phủ chỉ trả lời được mã nào chưa được chạm tới. Muốn biết kiểm thử ở phần đã chạm tới có thật sự kiểm gì, cần bước tiếp theo. Nhưng trước khi đi tiếp, đáng làm một việc rẻ: mở báo cáo độ phủ của phần mã quan trọng nhất, tìm các nhánh xử lý lỗi chưa tô màu, và nhờ AI viết ca cho đúng những nhánh đó.
Kiểm thử đột biến hoạt động thế nào
Ý tưởng rất đơn giản: nếu kiểm thử tốt, thì một thay đổi nhỏ làm sai mã phải làm ít nhất một kiểm thử đỏ. Công cụ kiểm thử đột biến tự động tạo ra hàng loạt bản mã bị sửa nhỏ, gọi là đột biến: đổi lớn hơn hoặc bằng thành lớn hơn, đổi cộng thành trừ, đổi hằng số, đảo điều kiện, xoá một lời gọi hàm. Với mỗi đột biến, công cụ chạy bộ kiểm thử. Đột biến làm kiểm thử đỏ gọi là bị diệt; đột biến mà kiểm thử vẫn xanh gọi là sống sót.
Mỗi đột biến sống sót là một câu hỏi cụ thể: có một thay đổi làm sai mã ở dòng này mà không kiểm thử nào phát hiện. Hoặc bạn thiếu một ca kiểm thử, hoặc câu kiểm hiện có quá lỏng, hoặc hiếm hơn, đột biến đó không thực sự đổi hành vi. Tỉ lệ đột biến bị diệt là thước đo chất lượng kiểm thử sát thực tế hơn nhiều so với độ phủ.
Công cụ theo ngôn ngữ có thể kể vài cái tên được dùng rộng rãi: mutmut cho Python, Stryker cho JavaScript và TypeScript, Infection cho PHP, PIT cho Java. Cách cài đặt và cấu hình thay đổi theo phiên bản; đọc tài liệu chính thức trước khi dùng. Nhược điểm chung là chậm, vì bộ kiểm thử phải chạy lại cho mỗi đột biến.
Ví dụ giả định: một đột biến sống sót
Hàm kiểm đơn có được miễn phí giao hàng không, với mốc trong ví dụ là giả định. AI viết hàm kèm hai ca kiểm thử:
def duoc_mien_phi(tong_don):
return tong_don >= 500_000
def test_don_lon_duoc_mien_phi():
assert duoc_mien_phi(800_000) is True
def test_don_nho_khong_duoc_mien_phi():
assert duoc_mien_phi(100_000) is False
Độ phủ: 100%. Công cụ đột biến đổi dấu lớn hơn hoặc bằng thành lớn hơn, chạy lại: cả hai ca vẫn xanh, vì 800.000 và 100.000 đều cách xa mốc. Đột biến sống sót, và nó chỉ đúng vào lỗi mà một người sửa mã vô ý có thể gây ra: đơn đúng bằng mốc không còn được miễn phí. Thêm một ca diệt nó:
def test_don_dung_bang_moc_duoc_mien_phi():
assert duoc_mien_phi(500_000) is True
Một đột biến khác có thể đổi mốc thành 500.001 hay 499.999; ca mới cũng diệt được kiểu đổi lên, còn kiểu đổi xuống cần thêm ca ngay dưới mốc. Đây là cách kiểm thử đột biến dạy bạn ca biên một cách cụ thể, thay vì dựa vào trí nhớ.
Bài học rộng hơn ví dụ này: AI hay chọn giá trị thử tròn trịa, cách xa mốc, vì chúng trông giống ví dụ minh hoạ. Kiểm thử như vậy kiểm được chiều hướng nhưng không kiểm được ranh giới, mà ranh giới lại là nơi lỗi hay nằm. Khi giao việc viết kiểm thử, dặn AI luôn có ca đúng tại mốc, ngay dưới và ngay trên mốc cho mọi phép so sánh.
Dùng AI xử lý danh sách đột biến còn sống
Báo cáo đột biến của một mô-đun vừa phải có thể liệt kê vài chục đột biến sống sót. Đây là việc AI giúp rất hiệu quả, với quy trình:
- Chạy công cụ đột biến trên một mô-đun, không phải cả dự án. Xuất danh sách đột biến sống sót kèm tệp, dòng và kiểu thay đổi.
- Đưa cho AI danh sách đó, mã của mô-đun, tệp kiểm thử hiện có và đặc tả. Yêu cầu phân loại từng đột biến: thiếu ca kiểm thử, câu kiểm quá lỏng, hay đột biến tương đương không đổi hành vi, kèm lý do.
- Đọc phân loại. Đột biến tương đương, ví dụ đổi thứ tự hai phép gán độc lập, thì bỏ qua. Những cái còn lại xác nhận với đặc tả.
- Yêu cầu AI viết ca kiểm thử nhắm đúng từng đột biến, mỗi ca có tên nói rõ quy tắc nó bảo vệ, giá trị mong đợi lấy từ đặc tả chứ không từ mã.
- Chạy lại công cụ đột biến để xác nhận các đột biến đó đã bị diệt, và chạy bộ kiểm thử bình thường để chắc không có ca nào hỏng.
- Gộp thành một thay đổi riêng, ghi tỉ lệ diệt trước và sau trong mô tả.
Điểm cần cảnh giác ở bước bốn: AI có thể viết ca diệt được đột biến bằng cách khẳng định đúng hành vi hiện tại của mã, kể cả khi hành vi đó sai đặc tả. Vì vậy giá trị mong đợi luôn phải kiểm với đặc tả, như mọi kiểm thử khác.
Đưa vào quy trình mà không làm chậm cả nhóm
Kiểm thử đột biến tốn thời gian chạy, nên cách đưa vào quyết định nó có sống được trong nhóm hay không:
- Bắt đầu với một hai mô-đun rủi ro cao nhất như tính tiền hoặc phân quyền, chạy bằng tay vài lần để nhóm làm quen với cách đọc báo cáo.
- Khi đưa vào tự động, chỉ chạy đột biến trên phần mã thay đổi trong yêu cầu gộp mã; nhiều công cụ có tuỳ chọn kiểu này.
- Chạy toàn bộ theo lịch, ví dụ hằng tuần vào ban đêm, và theo dõi chiều hướng tỉ lệ diệt.
- Chưa đặt ngưỡng chặn gộp mã ở giai đoạn đầu; chỉ hiển thị kết quả để người rà tham khảo.
- Giữ bộ kiểm thử đơn vị nhanh, vì thời gian chạy đột biến nhân lên theo thời gian chạy kiểm thử.
Với nhóm nhỏ, chỉ cần thói quen chạy kiểm thử đột biến trên mô-đun vừa viết xong, trước khi gộp, đã đủ thay đổi chất lượng bộ kiểm thử AI viết. Không cần biến nó thành một tầng bắt buộc nếu nhóm chưa sẵn sàng.
Câu hỏi thường gặp
Độ phủ 100% có nghĩa là mã không còn lỗi không?
Không. Độ phủ 100% chỉ nói mọi dòng đã được chạy qua khi kiểm thử chạy, không nói kết quả của chúng được kiểm đúng. Ví dụ trong bài cho thấy hàm đạt độ phủ 100% mà vẫn để lọt lỗi ở đúng mốc quan trọng nhất.
Kiểm thử đột biến có quá chậm cho dự án lớn không?
Chạy cho cả dự án lớn có thể mất hàng giờ. Vì thế người ta chỉ chạy trên phần mã thay đổi ở mỗi yêu cầu gộp mã, và chạy toàn bộ theo lịch. Giữ kiểm thử đơn vị nhanh cũng làm kiểm thử đột biến nhanh theo.
Đột biến tương đương là gì?
Là đột biến làm mã khác đi về hình thức nhưng không đổi hành vi, nên không kiểm thử nào có thể diệt được. Ví dụ đổi thứ tự hai lệnh độc lập. Những đột biến này được đánh dấu bỏ qua, không cần viết kiểm thử cho chúng.
Có nên đặt ngưỡng tỉ lệ diệt đột biến để chặn gộp mã không?
Ở giai đoạn đầu thì chưa nên, vì nhóm cần thời gian hiểu báo cáo và loại đột biến tương đương. Khi đã quen, có thể đặt quy tắc không để tỉ lệ diệt của mô-đun quan trọng giảm, thay vì một con số tuyệt đối cho cả dự án.
AI có tự chạy được kiểm thử đột biến không?
Tác tử AI có quyền chạy lệnh có thể chạy công cụ và đọc báo cáo. Nhưng việc phân loại đột biến và quyết định giá trị mong đợi vẫn cần người kiểm với đặc tả, vì AI dễ viết ca diệt đột biến bằng cách khẳng định hành vi hiện tại của mã.
Có nên tự gài lỗi bằng tay thay cho công cụ không?
Với một hàm nhỏ vừa viết, gài vài lỗi bằng tay ở các mốc quan trọng là cách nhanh và dạy bạn nhiều. Khi mã lớn dần, công cụ làm việc đó đều tay và đầy đủ hơn. Hai cách bổ sung cho nhau chứ không loại trừ.