虚拟包无需也无法写单元测试,因其无代码、不参与自动加载;应测试其在真实项目中的依赖解析效果,如 conflict 行为、autoload 穿透及 composer show/depends 验证。

Composer 虚拟包(virtual package)本身不包含可执行代码,它只是通过 replace 或 provide 声明“替代”或“提供”某个包名,供其他包做依赖约束。因此你**无法也无需为虚拟包本身写单元测试**——它没有 src/、没有类、没有函数,phpunit 根本无处下手。
为什么 phpunit 会报 Class 'PHPUnitFrameworkTestCase' not found?
这是最常踩的坑:你照着普通 PHP 包的流程,在虚拟包里建了 tests/、写了 ExampleTest.php、配了 phpunit.xml,结果一跑就崩。根本原因不是 autoload 没配好,而是 Composer 虚拟包默认没声明任何自动加载规则 —— 即使你手动加了 "autoload": {"psr-4": {"Tests\": "tests/"}},vendor/autoload.php 也不会加载 Tests 命名空间,因为虚拟包不参与自动加载注册(它只参与依赖解析)。
- Composer 只在安装“真实包”时执行 autoload 注册逻辑;虚拟包被跳过
-
composer require --dev phpunit/phpunit能装上,但./vendor/bin/phpunit启动后找不到你的测试类,报错Class 'PHPUnitFrameworkTestCase' not found或0 tests executed - 强行加
"autoload-dev"并运行composer dump-autoload也无效:虚拟包的 autoload 配置在composer install阶段就被忽略
真正该测的是“使用虚拟包的项目”,不是虚拟包本身
虚拟包的价值体现在它如何影响下游项目的依赖解析和行为。所以测试重点必须转移到实际消费它的项目上:
- 新建一个真实项目(比如
test-consumer),composer require你的虚拟包(如myorg/virtual-logger) - 再
composer require --dev monolog/monolog或其他被虚拟包replace的真实实现 - 写测试验证:当项目同时 require 虚拟包 + 真实实现时,Composer 是否拒绝安装(应报 conflict)、是否允许共存(取决于
replace写法)、是否能正常new MonologLogger(验证 autoloading 是否穿透) - 用
composer show --tree和composer depends myorg/virtual-logger手动验证依赖图是否符合预期
如果非要验证虚拟包声明语法,用 composer validate + 手动解析
虚拟包唯一可“测试”的是它的 composer.json 是否合法、语义是否清晰。这不需要 PHPUnit:
- 运行
composer validate:检查 JSON 结构、字段合法性(如replace必须是对象,不能是字符串) - 检查
replace键是否覆盖了目标包的全部版本范围(例如"monolog/monolog": "*"而不是"monolog/monolog": "^2.0",否则旧版仍可被装入) - 用
composer depends --tree在真实项目中验证:当你 require 虚拟包后,原依赖是否真的从锁文件中消失 - CI 中可加 shell 断言:
! composer show monolog/monolog 2>/dev/null应该成功(表示已被 replace 掉)
虚拟包不是代码容器,它是依赖图的“策略声明”。把测试精力放在声明是否生效、下游是否按预期解析,比硬塞一个跑不通的 TestCase 实在得多。


















