3、PHPUnit配置:phpunit.xml配置文件详解、测试套件(TestSuite)组织、过滤器与分组

好,咱们直接进入正题。PHPUnit 的配置,说白了就是通过一个 XML 文件告诉它:「嘿,你要怎么跑测试、跑哪些测试、跑完怎么报告」。这个文件就是 phpunit.xmlphpunit.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>

然后日常开发只跑 UnitFeature,只有改到支付或报表模块时才跑 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% 的使用场景。先把这些用好,你的测试效率至少翻一倍。