第1章:测试双胞胎家族概览
什么是测试双胞胎?
在单元测试、集成测试或组件测试中,被测系统(SUT)往往依赖外部组件:数据库、网络服务、文件系统、第三方API等。为了隔离被测逻辑、加速执行并消除不确定性,团队引入了五类“替身对象”——统称为测试双胞胎(Test Doubles)。该术语源自电影中“替身演员”的概念:用一个可控制的替身替代真实角色,让主镜头(核心逻辑)不受干扰。
为什么需要区分? 许多团队混用“Mock”指代所有替身,导致测试意图模糊、维护成本上升。明确各类角色,才能让测试代码既自描述又可预测。
五类角色详解
| 角色 | 定义 | 常见用途 | 是否使用真实依赖 |
|---|---|---|---|
| Dummy | 仅用于填充参数列表,不参与任何实际交互 | 构造方法参数、空集合、无意义的占位对象 | 否(完全不使用) |
| Stub | 提供预设返回值,不验证调用行为 | 查询类接口、读取配置、返回固定数据 | 部分替代(仅返回预设值) |
| Spy | 记录调用信息(次数、参数、顺序),可返回真实或预设值 | 验证某方法是否被调用、记录日志、审计 | 部分替代(真实逻辑可被包装) |
| Mock | 预先设定期望(期望调用的方法、参数、次数),并验证期望是否满足 | 行为驱动测试、验证交互的正确性 | 完全替代(由框架生成) |
| Fake | 实现真实组件简化版的功能(如内存数据库代替真实数据库) | 需要完整但轻量的替代实现(仓库、缓存、队列) | 部分替代(逻辑等价但简化) |
关键区分维度:
• 是否验证行为:Mock / Spy(验证) vs Stub / Dummy / Fake(不验证)
• 是否保留真实逻辑:Fake 保留简化逻辑,Stub/Spy 仅返回设定,Dummy 无逻辑,Mock 由框架生成期望逻辑。
• 是否验证行为:Mock / Spy(验证) vs Stub / Dummy / Fake(不验证)
• 是否保留真实逻辑:Fake 保留简化逻辑,Stub/Spy 仅返回设定,Dummy 无逻辑,Mock 由框架生成期望逻辑。
核心区别对比
- Dummy vs 其他: Dummy 从不被使用,甚至不需要实现接口方法(常抛异常);其他角色都会被实际调用。
- Stub vs Spy: Stub 只负责输出,不记录任何人;Spy 会记录调用痕迹,便于事后检查。
- Spy vs Mock: Spy 是被动记录,测试结束后手动断言;Mock 是主动设定期望,在验证阶段由框架自动检查所有期望是否满足。
- Fake vs 其余: Fake 拥有可用的业务逻辑(如内存 List 模拟数据库),而 Stub/Spy/Mock 通常只返回固定值或委托给框架。
常见误区
- 误区:“Mock就是一切测试替身”
实际上 Mock 只是五类之一,用于验证交互行为;过度使用 Mock 会导致测试与实现紧耦合。 - 误区:“Stub也能验证调用次数”
Stub 默认不记录调用信息;若需要验证次数,应使用 Spy 或 Mock。 - 误区:“Fake就是简化版Stub”
Fake 拥有真实业务逻辑(如权限校验、排序),而 Stub 通常只返回固定数据,不具备逻辑。
练习题(自检)
- 以下场景最适合使用哪种测试双胞胎?
a) 测试订单服务,需要检查“发送邮件”方法是否被调用了1次。
b) 测试用户服务,需要一个“可用的用户仓库”来执行增删改查,但不希望连接真实数据库。
c) 测试报告生成器,参数列表中有一个“配置对象”,但该配置在测试中完全不会被用到。 - 判断:Spy 和 Mock 的区别在于 Mock 必须在测试开始前设定期望,而 Spy 可以在测试结束后进行断言。 (True/False)
- 重写以下错误测试描述:“我们使用 Mock 来模拟数据库返回固定用户列表”。应该使用什么角色?
应用边界
- Dummy:适用于任何必须传入但不会被真正使用的参数。例如 Java 中的
null或空对象,但注意避免NullPointerException。 - Stub:适用于“读”接口,如配置读取、远程查询、数据源快照。不适合有副作用(写入、发送)的场景。
- Spy:可用于监控行为但不改变返回值。比如日志记录、权限检查。小心多次包装导致测试脆弱。
- Mock:适合交互协议严格、接口调用次数/顺序重要的场景。不适合返回值驱动的测试(应使用 Stub)。
- Fake:最接近真实实现,适合复杂业务逻辑的轻量替代。但维护成本较高,避免用于仅有简单返回值的场景。
边界警告:当被测逻辑与外部交互非常丰富时,不要混合使用多个 Stub/Spy/Mock 导致测试难以理解。推荐按测试意图选择单一角色,或按分层分别隔离。