False kill trong mutation testing: baseline, truy cập tệp, lý do kill
Đăng ngày 2026-10-27
Mutation testing cố ý thay đổi code của bạn (< thành <=, return x thành return None) rồi kiểm tra xem các test có phát hiện ra không. Một mutant "killed" nghĩa là có một test thất bại. Nhưng một test có thể thất bại vì những lý do chẳng liên quan gì đến đột biến — và khi đó điểm mutation của bạn trông tốt hơn so với chất lượng thật của các test. Hướng dẫn này nêu ba cách kiểm tra để bắt các false kill (kill giả) này.
Vì sao false kill xảy ra
| Nguyên nhân | Bạn thấy gì |
|---|---|
| Bộ test đã thất bại hoặc chập chờn (flaky) từ trước khi đột biến | Nhiều kill với cùng một lỗi không liên quan |
| Test import tệp gốc, không phải bản sao đã đột biến | Kill chỉ đến từ timeout hoặc lỗi môi trường |
| Timeout do máy chậm | Kill tập trung ở các test chạy lâu |
| Trạng thái dùng chung giữa các lần chạy (tệp tạm, cache, cổng) | Kết quả kill thay đổi khi bạn chạy lại cùng một mutant |
Timeout ảnh hưởng đến điểm số: trong Stryker, detected = killed + timeout, và mutation score = detected / valid × 100. Một máy timeout nhiều sẽ làm điểm bị thổi phồng. Nguồn: Stryker mutant states and metrics, kiểm tra 2026-10-19.
Kiểm tra 1 — Baseline trên bản sao nguyên bản
- Sao chép dự án sang một thư mục mới, đúng như cách công cụ mutation sẽ làm (cùng sandbox, cùng biến môi trường).
- Chạy toàn bộ bộ test ở đó mà không có đột biến, ba lần.
- Cả ba lần đều phải vượt qua. Bất kỳ thất bại nào ở đây là một thất bại có sẵn từ trước, về sau sẽ hiện ra thành các kill giả — hãy sửa hoặc cách ly nó trước.
cp -r project /tmp/mt-baseline && cd /tmp/mt-baseline
for i in 1 2 3; do pytest -q -p no:cacheprovider || echo "BASELINE FAIL $i"; done
Kiểm tra 2 — Chứng minh các test đã đọc tệp bị đột biến
Nếu một test không bao giờ nạp tệp bị đột biến thì nó không thể đã kill mutant đó. Hãy ghi lại việc truy cập tệp trong mỗi lần chạy mutant. Trong Python, một audit hook là đủ:
# conftest.py
import sys, os
TARGET = os.environ.get("MUT_TARGET") # path of the mutated file
def hook(event, args):
if event == "open" and TARGET and str(args[0]).endswith(TARGET):
open("/tmp/mut_touched", "a").write("1")
sys.addaudithook(hook)
Chạy mỗi mutant sau khi đã xóa bytecode cũ (xóa __pycache__ trong bản sao) để tệp nguồn thực sự được đọc. Quy tắc: một kill mà tệp mục tiêu chưa từng được mở thì bị đánh dấu là suspect (đáng ngờ) và chạy lại. Trên các nền tảng khác, strace -e trace=openat (Linux) hoặc dữ liệu coverage của test runner cũng phục vụ cùng mục đích.
Kiểm tra 3 — Xem phân bố lý do kill
- Với mỗi mutant bị kill, lưu lại vì sao lần chạy thất bại: assertion error, exception khác, timeout, crash, lỗi collection/import.
- Lập một bảng đếm đơn giản.
| Lý do | Mẫu lành mạnh | Dấu hiệu đáng ngờ |
|---|---|---|
| Assertion thất bại | Phần lớn các kill | — |
| Exception khác | Một số | Một thông báo giống hệt nhau trên nhiều mutant |
| Timeout | Ít (các mutant gây vòng lặp vô hạn) | Chiếm tỷ lệ lớn; tăng lên trên máy chậm hơn |
| Lỗi import / collection | Gần như không có | Bất kỳ tỷ lệ đáng kể nào — chính harness, chứ không phải các test, đang thất bại |
Tổng hợp lại
- Baseline xanh 3/3 trên bản sao nguyên bản
- Mỗi kill đều có bản ghi "tệp mục tiêu đã được mở"
- Assertion thất bại là lý do kill lớn nhất
- Chạy lại ngẫu nhiên 5% số kill; kết quả không được thay đổi
Câu hỏi thường gặp
- Tôi có nên tăng timeout không?
- Hãy đặt nó dựa trên baseline: đo thời gian bình thường của bộ test, rồi cho phép một bội số của nó. Quá thấp tạo ra kill giả; quá cao làm chậm lần chạy.
- Lỗi biên dịch có được tính không?
- Trong Stryker, lỗi biên dịch và lỗi thời gian chạy là các mutant "invalid" và không nằm trong điểm số.
Nguồn
- Stryker: trạng thái mutant và các chỉ số — đã kiểm tra 2026-10-19
Liên quan (tiếng Hàn): Tổng hợp kiến thức phát triển (개발 지식 모음, tiếng Hàn)
🌐 English · Español · Bahasa Indonesia · 日本語 · ไทย · Tiếng Việt · 中文