Test Doubles 5 种 = 测试替身的智慧
解决什么问题:不知道该用 Mock 还是 Fake—— Test Doubles 5 种各有适用场景,错用 = 测试失效。
业务问题
真实场景
场景 1:Mock 一切的代价
python
# 反模式:Mock 一切
def test_order_service():
mock_db = Mock()
mock_payment = Mock()
mock_email = Mock()
mock_logger = Mock()
# 测的不是真实逻辑
# 是"我告诉 mock 返回什么"问题:测试与生产脱节,重构就坏。
Test Doubles 5 种(Google 定义)
| 类型 | 用途 | 例子 |
|---|---|---|
| Dummy | 占位参数 | None、"" |
| Fake | 可工作的假实现 | 内存数据库 |
| Stub | 返回预设值 | stub.get() returns 42 |
| Spy | 记录调用 | "调用了 get() 1 次" |
| Mock | 验证行为 | "必须调 send_email()" |
核心概念
何时用什么
能用真实 → 用真实(首选)
真实对象太慢 → Fake
真实对象有副作用 → Stub
要验证调用 → Spy
要验证行为契约 → Mock反模式
| 反模式 | 后果 |
|---|---|
| Mock 一切 | 测试与生产脱节 |
| 测实现而非行为 | 重构就坏 |
| Mock 私有方法 | 测的是细节 |
工程实践
决策树
能用真实对象吗?
- 是 → 用真实
- 否(太慢/不可靠) → 继续
对象只是返回数据?
- 是 → Fake(内存实现)
对象调用外部系统?
- 是 → Stub(预设返回值)
要验证是否被调用?
- 是 → Spy
要验证调用顺序/参数?
- 是 → Mock与 L 层对位
性能指标
| 指标 | 健康值 |
|---|---|
| Fake 占比 | > 50% |
| Mock 占比 | < 20% |
| 测试稳定性 | flaky < 1% |
关键洞察
- Fake > Mock——能 Fake 就 Fake。
- Mock 测行为,不是实现——别测私有方法。
- 真实对象能用就用——最可靠。
- 与 cybernetics 同构——Ch 6 §6.3 解耦 = 测试替身 = 隔离被测单元。