3、PHPUnit配置:phpunit.xml配置文件详解、测试套件(TestSuite)组织、过滤器与分组
好,咱们直接进入正题。PHPUnit 的配置,说白了就是通过一个 XML 文件告诉它:「嘿,你要怎么跑测试、跑哪些测试、跑完怎么报告」。这个文件就是 phpunit.xml 或 phpunit.xml.dist。我个人习惯把 .dist 版本提交到 Git,然后每个人本地可以复制一份去掉 .dist 来覆盖配置——这样团队配置统一,又保留了本地灵活性。
phpunit.xml 配置文件详解
先看一个最基础的配置骨架:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory>src</directory>
</include>
</coverage>
</phpunit>
这里每个属性都有它的脾气。我挑几个重点说说:
- bootstrap:引导文件。通常是 Composer 的 autoload。没有它,你的测试类根本找不到被测代码。
- colors:设为
true后,终端输出会带颜色。绿色通过、红色失败,一眼就能扫出来。 - verbose:啰嗦模式。开启后每个测试方法的名字都会打印出来。调试时很有用,但平时跑全量测试时我一般关掉,不然刷屏刷得眼花。
- failOnRisky / failOnWarning:这两个我建议都设为
true。为什么?因为「有风险」的测试(比如没做任何断言)或者「有警告」的测试(比如用了@depends但依赖的测试失败了),其实都是代码质量的隐患。我曾经在一个遗留项目里发现几十个「空测试」——方法名写好了,断言没写,测试永远通过。开了这个开关,它们就藏不住了。
重要属性速查表
| 属性 | 说明 | 我常用的值 |
|---|---|---|
| colors | 终端颜色输出 | true |
| verbose | 详细输出 | false(CI 里开 true) |
| failOnRisky | 有风险测试算失败 | true |
| failOnWarning | 有警告测试算失败 | true |
| stopOnFailure | 遇到第一个失败就停 | false(调试时开 true) |
| processIsolation | 每个测试在独立进程跑 | false(极慢,慎用) |
小技巧:如果你在 IDE 里跑测试,建议在 phpunit.xml 里加上 cacheResult="false"。不然有时候改了代码,测试结果还是缓存的旧数据,容易让人抓狂。
测试套件(TestSuite)组织
测试套件,说白了就是「把哪些测试文件归为一组」。你可以按目录、按文件、甚至按命名空间来组织。
最常见的做法是按类型分:
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
这样你就可以只跑某一部分测试:
# 只跑单元测试
vendor/bin/phpunit --testsuite Unit
# 只跑集成测试
vendor/bin/phpunit --testsuite Integration
我在项目中遇到过一种情况:某个模块的测试特别慢(因为要连数据库),而其他测试很快。这时候我会单独给这个模块建一个套件:
<testsuite name="Slow">
<directory>tests/Feature/Payment</directory>
<directory>tests/Feature/Report</directory>
</testsuite>
然后日常开发只跑 Unit 和 Feature,只有改到支付或报表模块时才跑 Slow。你想想看,这样能省多少时间?
注意:测试套件可以嵌套。但我不建议搞太深,两层就够了。套娃式的组织方式,维护起来你自己都晕。
过滤器(Filter)与分组(Group)
这两个功能,说白了就是「挑着跑测试」的两种姿势。
过滤器:按名字筛选
过滤器通过 --filter 参数,用正则匹配测试方法名或类名:
# 只跑方法名包含 "Order" 的测试
vendor/bin/phpunit --filter 'Order'
# 只跑 UserTest 类里的测试
vendor/bin/phpunit --filter 'UserTest'
# 更精确:只跑 UserTest 里的 testCreateUser 方法
vendor/bin/phpunit --filter 'UserTest::testCreateUser'
这个在调试单个测试时特别有用。我记得有一次排查一个诡异的 Bug,需要反复跑同一个测试方法。如果没有 --filter,每次都要等全量测试跑完——那感觉,就像用吸管喝一杯啤酒,急死人。
分组:给测试打标签
分组是用 @group 注解给测试方法或类打标签:
class OrderTest extends TestCase
{
/**
* @group slow
* @group database
*/
public function testCreateOrder()
{
// 这个测试需要连数据库,比较慢
}
/**
* @group fast
*/
public function testCalculateTotal()
{
// 这个测试纯计算,很快
}
}
然后在配置里声明分组:
<groups>
<include>
<group>fast</group>
</include>
<exclude>
<group>slow</group>
<group>database</group>
</exclude>
</groups>
或者直接在命令行指定:
# 只跑 fast 分组
vendor/bin/phpunit --group fast
# 排除 slow 分组
vendor/bin/phpunit --exclude-group slow
我的分组习惯
@group fast:纯逻辑测试,毫秒级完成@group slow:涉及 IO、数据库、外部 API@group smoke:冒烟测试,部署前必跑@group wip:正在开发的测试,还没写完
避坑指南:我曾经在项目里看到有人给每个测试方法都加了 @group,结果分组数量比测试方法还多。分组是「分类」不是「标签云」,控制在 5-8 个以内就够了。多了反而没人用。
组合使用:配置 + 套件 + 分组 + 过滤器
这几个东西可以组合起来用,威力更大。比如:
# 在 Unit 套件里,只跑 fast 分组,并且方法名包含 "Order"
vendor/bin/phpunit --testsuite Unit --group fast --filter 'Order'
嗯,这里要注意:过滤器、分组、套件是「与」的关系——必须同时满足所有条件才会执行。所以你可以非常精确地定位到你想跑的测试。
最后说一句:配置文件的学问其实不止这些。像 <php> 节点可以设置环境变量、<logging> 可以配置日志输出、<extensions> 可以注册自定义扩展。但今天讲的这三个——配置文件结构、测试套件、过滤与分组——已经覆盖了日常 90% 的使用场景。先把这些用好,你的测试效率至少翻一倍。