测试覆盖率白名单配置
解读
国内中大型 PHP 项目上线前普遍要求单测覆盖率 ≥ 80%,但框架入口、实体、配置等“样板代码”无需纳入统计。白名单(whitelist)就是告诉覆盖率驱动(Xdebug、PCOV、php-code-coverage)“哪些文件必须被统计”,其余文件一律忽略,从而避免“分母膨胀”导致覆盖率虚低。面试官问“怎么配”不只是考语法,而是看候选人是否能把“业务无关文件”精准剔除、保证 CI 卡点一次通过,同时不污染生产 opcache。
知识点
- php-code-coverage 组件(PHPUnit 9.5+ 内置)的 <coverage><include><directory> 节点
- Xdebug 3 模式与 PCOV 的 ENABLE 开关差异
- filter 的三种写法:directory、file、suffix,优先级与合并规则
- whitelist 与 blacklist(exclude)同时存在时的“先白后黑”策略
- CI 场景:phpunit.xml.dist 与 phpunit.xml 的继承关系,防止开发者在本地把白名单改松
- 路径基准:基于 phpunit.xml 所在目录的相对路径,Docker 内若 WORKDIR 不同需做软链或前缀修正
- 性能:白名单过长(>2k 文件)时 PCOV 比 Xdebug 快 30%,建议高并发流水线切换驱动
- 上报 SonarQube、Codecov 时,xml 报告里仍需包含白名单外的文件,但标记为 0%,需要把 clover 的 false 属性设为 true,否则质量门禁误判
答案
以 Laravel 8 项目为例,业务代码集中在 app/ 与 modules/,框架启动文件、路由缓存、数据库迁移、视图编译文件全部忽略,CI 强制要求行覆盖率 ≥ 80%。在项目根目录创建 phpunit.xml.dist:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<coverage cacheDirectory=".phpunit.cache/code-coverage"
processUncoveredFiles="true">
<!-- 白名单:只统计业务代码 -->
<include>
<directory suffix=".php">app</directory>
<directory suffix=".php">modules</directory>
</include>
<!-- 黑名单:再精确剔除 -->
<exclude>
<directory>app/Console/Commands/Stubs</directory>
<file>app/helpers.php</file>
<directory>modules/*/Resources/stubs</directory>
</exclude>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>
关键点
- include 节点即白名单,只有出现在这里的文件才会进入覆盖率分子与分母;exclude 在白名单范围内二次过滤。
- cacheDirectory 开启缓存,二次运行提速 40% 以上,CI 中把 .phpunit.cache 目录加入 actions/cache。
- processUncoveredFiles="true" 确保白名单内未被测试覆盖的文件也计入分母,防止“只测一个文件就 100%”的作弊。
- 若使用 PCOV,在 GitHub Actions 加一步:
可让 10 万行代码的覆盖率采集从 90s 降到 12s。- name: Enable PCOV run: | sudo phpdismod xdebug sudo phpenmod pcov echo "pcov.enabled=1" | sudo tee -a /etc/php/${{ matrix.php }}/mods-available/pcov.ini - 上线门禁脚本:
check-coverage.php 读取 coverage.xml 的 project->metrics['elements'] 与 coveredelements,不达标 exit(1) 即可阻断合并。vendor/bin/phpunit --coverage-clover=coverage.xml php check-coverage.php coverage.xml 80
拓展思考
- 微服务场景:各子服务用 monorepo 管理,如何在根目录统一白名单又允许服务自定义?
答:在 phpunit.xml 用<testsuite name="ServiceA">区分,每个服务下放 phpunit.service-a.xml,通过vendor/bin/phpunit -c phpunit.service-a.xml单独采集,SonarQube 多模块扫描即可。 - 本地开发想临时把覆盖率门槛降到 60%,但禁止提交:
答:把 phpunit.xml 加入 .gitignore,本地随意改;CI 强制使用 phpunit.xml.dist,防止“放水”合并。 - 行覆盖、分支覆盖、路径覆盖混用:
白名单只能控制“哪些文件”,分支覆盖率需 PHPUnit >=10 并打开--coverage-branches,此时要把 include 范围进一步缩到核心业务类,否则分支爆炸导致报告过大,GitLab 页面会 502。 - 生产环境 opcache 预加载:
白名单文件越少,预加载脚本越容易维护;可把 app/ 与 modules/ 目录打包为 preload.php,忽略 tests/ 与 vendor/,既加速又避免把测试类带进 opcache。