直接 new 类导致测试困难,因其硬编码依赖(如 new database())无法替换,迫使单元测试调用真实外部服务,造成慢、不稳定、数据污染;应通过依赖注入(构造函数/参数传入接口)、拆分有状态逻辑、使用部分 mock 和可配置服务来提升可测性。

为什么直接 new 一个类会让测试变困难
因为硬编码依赖无法替换,比如在类里写 new Database() 或 new HttpClient(),单元测试时就不得不连真实数据库或网络一起跑——这既慢又不稳定,还容易污染数据。
解决办法是把外部依赖通过构造函数或方法参数传进来,也就是「依赖注入」。这样测试时可以传入模拟对象(Mock),控制返回值、验证调用行为。
- 把
new操作从类内部移到调用处,或交给工厂/容器 - 类的构造函数参数应声明具体接口(如
LoggerInterface),而非具体实现(如FileLogger) - 避免在构造函数里做重操作(如连接数据库、读配置文件),否则
new MyClass()本身就会失败或变慢
如何为有状态的方法写可测代码
比如一个订单服务要「扣减库存并生成订单」,如果逻辑全堆在 placeOrder() 里,很难单独验证库存校验是否正确、是否真的调用了支付网关。
应该拆成小而专注的方法,并让它们可独立调用和断言:
- 把校验逻辑抽成
canPlaceOrder(),返回布尔值,测试它对各种输入的响应 - 把外部调用封装成 protected 方法(如
callPaymentGateway()),方便在测试中被子类或 Mock 覆盖 - 避免在方法内直接修改全局状态(如
$_SESSION、静态变量),改用显式传参或依赖注入的状态管理器
PHPUnit 中怎么 mock 一个依赖但保留部分真实行为
有时你只想拦截某个方法(如 sendEmail()),其余方法仍走原逻辑——这时候不能用全量 Mock,得用「部分 Mock」(也叫 Test Double with partial mocking)。
在 PHPUnit 10+ 中,推荐用 createPartialMock():
$mailer = $this->createPartialMock(Mailer::class, ['sendEmail']);
$mailer->method('sendEmail')->willReturn(true);
// 其他方法(如 validateAddress())仍调用真实实现
- 注意:被 mock 的方法必须是 public 或 protected;private 方法无法被部分 mock
- 如果类里有构造函数副作用(如连接 Redis),先用
disableOriginalConstructor()避免初始化失败 - 慎用
getMockBuilder()->setMethods()(已废弃),它在新版本中行为不一致
测试中读取配置或环境变量怎么办
硬写 getenv('DB_HOST') 或 parse_ini_file('config.ini') 会导致测试环境不可控、难以复现问题。
统一收口到一个可注入的配置服务里:
- 定义接口
ConfigProviderInterface,提供get(string $key)方法 - 生产用
EnvConfigProvider读环境变量,测试用ArrayConfigProvider直接传数组 - 避免在测试中修改全局
$_ENV或调用putenv(),这会影响其他测试用例的执行顺序
真正难的不是写测试,而是让业务逻辑不跟运行环境、框架细节、全局状态搅在一起。越早把「做什么」和「怎么做」分开,后续加测试、换数据库、迁移到 Swoole 就越少返工。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











