PHP 项目怎么用 Infection?— 从零开始的 Mutation Testing 实战指南
目录导读(Table of Contents)
- 什么是 Infection?为什么 PHP 开发者需要它?
- 核心概念速览:Mutation Testing(变异测试)与代码覆盖率的关系
- 环境准备:安装 Infection(Composer 与 Phar 双方案)
- 实战配置:
infection.json5文件深度解析(含超集配置) - 运行你的第一次变异测试:命令行参数与 CI 集成技巧
- 解读报告:MSI(变异得分指数)如何指导你编写更健壮的测试?
- 常见坑与性能优化:如何让 Infection 跑得飞快?
- 高频问答(FAQ):解决你 90% 的疑惑
什么是 Infection?为什么 PHP 开发者需要它?

很多 PHP 开发者都有这样的经历:代码覆盖率(Code Coverage)达到了 90%,但上线后依然出现 bug,原因很简单——覆盖率只能证明“代码被执行了”,无法证明“测试真的验证了行为”,你的测试可能调用了 if 语句的分支,但从未断言 else 分支的结果。
Infection 是一款专为 PHP 设计的 Mutation Testing(变异测试)框架,它的核心逻辑是:故意往你的源码里“投毒”(例如把 改成 ,或把 改成 ),然后运行你的测试套件,如果测试没有失败,说明这个“变异体”存活了,你的测试存在漏洞。
简而言之:Infection 是测试的试金石,它能找出那些“假绿”的测试用例。
核心概念速览:Mutation Testing 与代码覆盖率的关系
- 变异体(Mutant):对源代码进行微小改动后的版本。
- Killed(被杀死的):测试发现了变异,导致测试失败。
- Escaped(逃脱的):测试未发现变异,测试依然通过——这是最危险的信号。
- MSI(Mutation Score Indicator):变异得分 = 被杀死的变异数 / 总变异数。80% 以上的 MSI 是健康的标准,90% 以上则为优秀。
关键区别:代码覆盖率(如 Xdebug)是“静态”指标,Mutation Testing 是“动态”验证。覆盖率是底线,MSI 是质量天花板。
环境准备:安装 Infection(Composer 与 Phar 双方案)
方案 A:Composer 全局安装(推荐用于 CI 环境)
composer global require infection/infection export PATH="$PATH:$HOME/.composer/vendor/bin"
方案 B:Phar 包(适合隔离环境)
wget https://github.com/infection/infection/releases/latest/download/infection.phar chmod +x infection.phar sudo mv infection.phar /usr/local/bin/infection
安装后验证:
infection --version
实战配置:infection.json5 文件深度解析
在项目根目录创建 infection.json5 文件:
{
"$schema": "https://raw.githubusercontent.com/infection/infection/main/resources/schema.json",
"source": {
"directories": [ // 指定要被变异的源码目录
"src"
],
"exclude": [ // 排除模板或生成代码
"src/Generated"
]
},
"timeout": 10, // 单个测试超时时间(秒)
"logs": {
"text": "build/infection.log", // 文本日志
"summary": "build/infection-summary.txt"
},
"mutators": {
"global-ignore": [ // 忽略某些特定规则,例如忽略数组弹栈操作
"ArrayPop"
],
"@default": true // 启用所有默认变异器
},
"minMsi": 85, // 最低 MSI 要求,低于此值 CI 会失败
"minCoveredMsi": 90 // 针对已覆盖代码的最低 MSI
}
高级配置技巧:如果项目使用了 Laravel,建议在 bootstrap 中添加 "extensions": ["infection/extension-installer"] 来自动适配框架。
运行你的第一次变异测试:命令行参数与 CI 集成技巧
基础运行(默认会询问是否生成配置,此时直接传递配置即可):
infection --configuration=infection.json5 --threads=4
常用参数:
--threads=4:并行执行,大幅提升速度(CPU 核心数减 1)。--only-covered:只对代码覆盖率已达到的代码行进行变异,节省时间。--show-mutations:显示具体的变异细节(如哪行被改成了什么)。--git-diff-lines:只变异本次 Git 提交中修改的行(配合 CI 分支检查)。
CI 集成示例(GitHub Actions):
- name: Run Infection
run: |
vendor/bin/infection --min-msi=80 --min-covered-msi=85
解读报告:MSI 如何指导你编写更健壮的测试?
运行后,打开 build/infection.log,你会看到类似输出:
Escaped mutants:
=================
1) /path/to/src/Calculator.php:12 [M] PlusMinus (将 + 改为 -)
--- Original
+++ New
@@ @@
public function add(int $a, int $b): int
- return $a + $b;
+ return $a - $b;
应对策略:
- 如果出现
Escaped,你的测试缺少断言,例如上述场景,测试只检查了返回值是否为正数,但没检查具体数值,修复方法:断言assertEquals(3, $calculator->add(1, 2))。 - MSI 低于设定值,优先处理
Escaped数量最多的文件,而不是盲目追求覆盖率。
常见坑与性能优化:如何让 Infection 跑得飞快?
- 坑 1:慢测试 → 优化:在
phpunit.xml中开启processIsolation或preserveGlobalState为false。 - 坑 2:变异爆炸 → 优化:先使用
--only-covered或结合--git-diff-lines缩小范围。 - 坑 3:MongoDB/SQLite 扩展问题 → 优化:在 CI 中启用
--skip-initial-tests跳过初始测试,或使用extensions配置忽略特定变异器(如DateTime相关)。
性能史诗级优化:在项目根目录添加 .infection 缓存文件(默认自动生成),并确保 vendor/autoload.php 使用 --classmap-authoritative 生成优化后的自动加载机制。
高频问答(FAQ)
Q1:Infection 和 PHPStan/Psalm 有什么区别?
A:PHPStan 是静态分析(找代码逻辑错误),Infection 是动态验证(找测试漏洞),两者互补,不冲突。
Q2:我的测试很完善,MSI 应该多少合适?
A:对于库或核心业务模块,建议不低于 80%;对于控制器或模板代码,60% 即可接受。关键看业务价值,不要为纯 getter/setter 追求 100%。
Q3:运行时间太长,能中断吗?
A:可以,Infection 支持断点续跑,完整跑完后进程会退出,中途 Ctrl+C 不会损坏任何数据。
Q4:有没有替代品?
A:有。Humbug 是老牌工具,但已停止维护。Deptrac 是架构层面的工具,与 Infection 不同层级。在 PHP 8.1+ 生态中,Infection 是事实标准。
Q5:如何排除某些特定函数不被变异?
A:在 infection.json5 的 mutators 块中声明 "global-ignore": ["DateModify"] 即可。
Infection 不是银弹,但它是 PHP 工程质量的重要补充,当你把 MSI 从 40% 提升到 85% 时,你会发现那些曾经“看起来没问题”的测试,其实在静默地漏掉真正的 bug,从今天起,把它加入你的 CI 流程吧。