🔍검색

首页 › 指南

变异测试的假杀死:基线、文件访问与杀死原因

发布日期 2026-10-27

变异测试(mutation testing)会故意修改你的代码(把 < 改成 <=,把 return x 改成 return None),然后检查你的测试是否能发现。“被杀死(killed)”的变异体意味着有测试失败了。但测试失败的原因可能与变异毫无关系——这样一来,你的变异得分看上去就比测试的真实水平更好。本指南介绍三项检查,用来发现这些假杀死(false kills)。

假杀死为什么会出现

原因你会看到什么
测试套件在变异之前就已经失败或不稳定(flaky)大量杀死都带有同一个无关的错误
测试导入的是原始文件,而不是被变异的副本杀死仅来自超时或环境错误
机器太慢导致超时杀死集中在运行时间长的测试上
多次运行之间共享状态(临时文件、缓存、端口)重新运行同一个变异体时,杀死结果会变化

超时会影响得分:在 Stryker 中,detected = killed + timeout,而变异得分 = detected / valid × 100。经常超时的机器会抬高得分。来源:Stryker 变异体状态与指标,核对于 2026-10-19。

检查 1 — 在原始副本上建立基线

  1. 把项目复制到一个全新的目录,完全按变异工具的方式来做(相同的沙箱、相同的环境变量)。
  2. 在那里不加任何变异,完整运行测试套件三次。
  3. 三次都必须通过。这里出现的任何失败都是预先存在的失败,之后会表现为假杀死——请先修复或隔离它。
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

检查 2 — 证明测试读取了被变异的文件

如果测试根本没有加载被变异的文件,它就不可能杀死那个变异体。请在每次运行变异体时记录文件访问。在 Python 中,使用审计钩子(audit hook)就够了:

# 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)

运行每个变异体时,先清除过期的字节码(删除副本中的 __pycache__),这样源文件才会真正被读取。规则:如果某次杀死中目标文件从未被打开,就把它标记为可疑(suspect)并重新运行。在其他技术栈上,strace -e trace=openat(Linux)或测试运行器的覆盖率数据可以起到同样的作用。

检查 3 — 查看杀死原因的分布

  1. 对每个被杀死的变异体,记录运行失败的原因:断言错误、其他异常、超时、崩溃、收集/导入错误。
  2. 做一张简单的计数表。
原因健康的模式危险信号
断言失败大多数杀死—
其他异常少部分许多变异体上出现同一条相同的消息
超时很少(无限循环的变异体)占比很大;在较慢的机器上上升
导入/收集错误几乎没有任何有意义的占比——说明失败的是测试框架,而不是测试

综合起来

常见问题

我应该调高超时吗?
根据基线来设定:测量测试套件的正常运行时间,然后允许其若干倍。太低会产生假杀死;太高会拖慢运行。
编译错误算数吗?
在 Stryker 中,编译错误和运行时错误属于“无效(invalid)”变异体,不计入得分。

来源

相关(韩语): 开发知识汇总(개발 지식 모음)

🌐 English · Español · Bahasa Indonesia · 日本語 · ไทย · Tiếng Việt · 中文

📤 공유