变异测试是什么
变异测试用工具自动修改源码生成「变异体」:把比较符替换成边界形式、取反条件、篡改返回值、删除语句。每个变异体都重跑一次现有测试——测试失败说明这个变异被「杀死」,测试仍通过说明存在检出盲区。核心指标是变异得分=被杀死变异体 ÷ 有效变异体总数,用它评估测试套件对人为错误的发现能力,而不是代码被执行的比例。
基本信息
| 核心指标 | 变异得分(Mutation Score),按可变异体扣掉等价变异后计算 |
|---|---|
| 常见工具 | PIT(JVM)、mutmut 与 cosmic-ray(Python)、Stryker(JS/TS、C#) |
| 成本特点 | 每个变异体都要重跑相关测试,属高成本分析,适合核心模块 |
| 常用策略 | 先限定高风险文件与包路径,再逐步扩大范围 |
| 等价变异 | 语义与原代码相同的变异,应标记排除,不计入得分 |
典型使用场景
- 评估测试漏洞:覆盖率很高但变异得分低,说明断言弱,只走了流程没验证结果
- 核心模块质量门禁:对计价、支付等模块设变异得分阈值,定期在流水线执行
- 验证新写的测试:新测试是否真能发现人为错误,用变异结果反查
- 定位断言缺失:大量存活变异集中在某个函数,提示那里断言太少或没断言
- 重构后抽查:重构不应改变行为,变异得分应保持稳定,突变说明测试被动过
- 培训与评审素材:用存活变异当具体例子,讨论测试该怎么改
Python 与 JVM 用法示意
Python 与 JVM 用法示意
pip install mutmut # 安装变异测试工具
mutmut run --paths-to-mutate src/ # 只对 src 目录做变异,控制范围
mutmut results # 查看被杀死与存活的变异体
# JVM 常用 PIT:mvn test org.pitest:pitest-maven:mutationCoverage
# JS/TS 常用 Stryker:npx stryker run
# 只给新增或改动文件的变异结果设置阈值,避免历史债务拖累门禁变异算子与验证重点
| 变异算子 | 改法示例 | 暴露的问题 |
|---|---|---|
| 边界替换 | 小于号换成小于等于号 | 边界用例缺失 |
| 条件取反 | 与运算改成或运算 | 分支组合覆盖不足 |
| 返回值篡改 | 返回空集合或空值 | 异常路径断言缺失 |
| 语句删除 | 删掉一次状态更新 | 副作用没被断言验证 |
| 常量替换 | 把阈值改成别的数字 | 配置类逻辑缺少等价类用例 |
变异得分不适合跨语言横向比较:不同工具、不同算子集、不同代码库的基线差异很大。正确用法是先测出本项目的基线,再跟踪趋势和薄弱模块变化。
覆盖率是执行过没有,变异得分是断言管不管用——两者互补。变异测试通常安排在夜间流水线或发布前,而不是每次提交都全量跑。