
本文详解单元测试中应优先使用 mock 隔离被测逻辑,避免外部依赖干扰;集成测试则应使用真实服务(如 ioc 容器提供的实例),以验证组件协同行为。二者目标不同,不可混用,合理选择能显著提升测试可靠性与可维护性。
本文详解单元测试中应优先使用 mock 隔离被测逻辑,避免外部依赖干扰;集成测试则应使用真实服务(如 ioc 容器提供的实例),以验证组件协同行为。二者目标不同,不可混用,合理选择能显著提升测试可靠性与可维护性。
在编写高质量测试时,明确测试类型与边界是首要前提。单元测试(Unit Test)的核心目标是验证单个类或方法的内部逻辑是否正确,因此必须严格隔离外部依赖——包括数据库、缓存、HTTP 客户端、消息队列等。此时,使用 createMock() 构造轻量级模拟对象是最优实践:
protected function setUp(): void
{
parent::setUp();
$this->delayReportService = new DelayReportService(
$this->createMock(RedisQueueStorageClientInterface::class),
$this->createMock(OrderRepository::class),
$this->createMock(DelayReportRepository::class),
$this->createMock(EstimatedArrivalTimeClientInterface::class),
$this->createMock(ParameterBagInterface::class)
);
}
该方式确保测试快速、稳定、可重复:不依赖 Redis 连接、不触发真实 SQL 查询、不调用远程 API,也无需预置测试数据库状态。
相反,若在 setUp() 中通过 self::getContainer()->get(...) 获取服务实例,则本质上已进入集成测试(Integration Test)范畴。这类测试旨在验证多个组件(如 Service + Repository + Client)在真实协作链路下的行为一致性。此时不应使用 Mock,而应启用完整测试环境(如内存 SQLite、TestContainer、Redis Docker 实例),否则将失去集成验证的意义。
⚠️ 注意事项:
- 勿混合滥用:在一个测试类中既从容器取服务,又对其中某依赖手动 Mock,会模糊测试意图,增加维护成本;
-
Mock 仅限间接依赖:若被测方法本身不使用某依赖(如
DelayReportService::__construct()注入了ParameterBagInterface,但当前测试方法完全未读取参数),仍建议 Mock —— 因构造函数执行属于类初始化环节,不 Mock 将导致无法实例化; -
替代方案更灵活:对难以 Mock 的基础设施(如 Redis 客户端),可引入内存实现(如
ArrayRedisClient)替代真实连接,在保持“真实交互”前提下规避环境依赖。
总结而言:单元测试 = Mock 所有协作者,聚焦逻辑;集成测试 = 使用真实服务(含 IOC 容器实例),聚焦协作。根据测试目标主动选择策略,而非机械套用,才是工程化测试的真正起点。










