首页/开发/代码质量与评审速查/变异测试(Mutation Testing):检验断言质量
技术债与度量63 条

变异测试(Mutation Testing):检验断言质量

变异测试自动制造小缺陷(改比较符、删调用)并检查测试是否失败:未被杀死的变异说明断言薄弱;Mutation Score 反映断言有效性,适合关键模块的测试体检。

变异测试在测什么

普通覆盖率回答「这行代码被执行了吗」,变异测试回答「测试有没有能力发现这行的逻辑被改坏」。工具自动对源码做微小改动(把小于号换成小于等于、删掉一次方法调用、对返回值取反),每产生一个变异体就完整跑一遍测试:测试失败称为变异被杀,测试仍然通过称为变异存活。存活意味着这处逻辑缺少有效断言。变异得分等于被杀变异数除以有效变异总数,用来衡量断言强度。

基本信息

原理自动注入小缺陷,用测试集检验是否被发现
常见工具PIT(JVM)、Stryker(JS/TS、C#)、mutmut(Python)
成本约等于变异体数量乘以单次测试时长
适用关键模块抽检,不做全仓例行

典型使用场景

  • 核心计价与结算逻辑:断言写得多但强度未知,用变异得分验证是否真能拦住改动
  • 覆盖率已高但线上仍有逃逸缺陷:说明执行到位而断言偏弱,用存活变异定位薄弱点
  • 接手遗留模块:先跑一轮变异测试,快速找出测试形同虚设的区域
  • 评审新增测试的充分性:对变更文件跑增量变异,看新断言是否覆盖了改动分支
  • 测试套件重构后回归验证:确认精简用例没有削弱发现缺陷的能力

Stryker 最小用法

Stryker 最小用法
npx stryker run            # 对配置范围内代码执行变异
npx stryker run --mutate src/pricing/*.ts   # 只测目标模块
# 报告分三类:killed(被杀)/ survived(存活)/ timeout(超时)
# survived 的条目就是需要补断言的位置

报告条目读法

状态含义正常与异常
Killed测试因变异失败,说明断言生效占比高是好事,是主要追求项
Survived测试仍通过,变异未被发现需逐条判断是否缺断言
Timeout变异导致死循环或超时少量正常,集中出现说明实现有问题
No coverage该变异体所在代码从未被测试执行属于覆盖缺口,先补用例
Runtime error变异导致编译或运行错误需要查看是否工具配置问题
不是所有存活变异都要修:等价变异体(改动后语义完全一致,例如把条件里冗余的括号顺序调整)天然无法被杀,属于噪声。处理时先判断这处变异的语义是否真的改变了。
成本控制是能否长期使用的关键。按模块轮换抽检、只对变更文件做增量变异、给测试集设超时,都能把单次运行压到分钟级;全仓库定时跑通常不划算。

落地检查清单

  • 先看存活变异的分布,再决定补断言还是调整设计
  • 对关键模块设定得分下限,作为门禁的一项
  • 把等价变异体登记进忽略清单,避免每次重复判断
  • 记录得分随时间的变化,而不是只看单次绝对值

易错点

  • 把变异得分当作质量总评:它只反映测试集发现缺陷的能力,不能替代缺陷密度与逃逸分析
  • 对全仓库例行执行:变异体数量乘以测试时长,运行时间不可接受,最后被整条流水线禁用
  • 把等价变异体计入缺口:误判为断言不足,反复补无意义的断言,浪费时间
  • 只盯着得分不区分模块:核心计价模块与工具函数用同一阈值,要么过松要么永远红灯

数据来源与参考

  • PIT、Stryker、mutmut 官方文档(变异测试工具)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌