过度打桩是什么
过度打桩指测试把被测对象内部的协作对象也替换成桩,用例因此与实现细节强耦合:调用顺序、内部方法名一变就失败,而对真实缺陷的检出价值有限。务实的边界是——只隔离外部依赖(网络、数据库、时间、随机、第三方 SDK),业务逻辑尽量使用真实对象。判断标准:替换的是进程外或不可控的依赖,还是被验证对象自己的一部分。
基本信息
| 问题本质 | 测试与实现耦合,重构即红灯,信号噪声大 |
|---|---|
| 可以替换 | 网络与数据库、时间与随机、第三方 SDK、消息队列 |
| 不要替换 | 被测对象的内部方法、同进程的业务对象 |
| 替代方案 | 容器化依赖、内存实现、契约测试、录制回放 |
| 预警信号 | 重构时大面积改测试,说明打桩过度 |
典型使用场景
- 外呼支付接口:用桩替代网络调用,避免不稳定与真实费用
- 时间相关逻辑:替换时钟实现,稳定构造月末、跨年、夏令时场景
- 随机与唯一标识:注入可控生成器,让断言可预期
- 数据库依赖:用容器或内存实现替代,或仅在集成层用真实库
- 第三方 SDK:通过适配层打桩,避免测试耦合 SDK 内部行为
- 重构评审:统计用例对内部实现的依赖数量,作为改进指标
只替换外部依赖的写法
只替换外部依赖的写法
# 只替换时钟,计价逻辑走真实实现
def test_fee_at_month_end(monkeypatch):
monkeypatch.setattr("app.clock.now", lambda: datetime(2026, 2, 28, 23, 59))
assert calc_fee(order) == expected
# 反例:把被测对象的内部方法也替换掉,用例与实现细节绑死
# monkeypatch.setattr(Order, "calculate_tax", lambda self: 1)
# 一旦内部方法改名或拆分,这条用例就会失败,但它并不代表业务错了替换边界对照
| 对象 | 建议做法 | 理由 |
|---|---|---|
| 网络与数据库 | 用桩或容器、内存实现 | 外部依赖,慢且不可控 |
| 时间与随机 | 替换为可控实现 | 不可控输入,用例需可复现 |
| 第三方 SDK | 先加适配层再打桩 | 隔离合作方接⼝变化 |
| 被测对象内部方法 | 避免替换,走真实实现 | 属于被测行为本身 |
与实现细节绑定的用例会在重构时集中失败,团队会逐渐失去对测试信号的信任:要么放弃维护,要么全员忽略红灯,这才是过度打桩最大的代价。
边界不是「零替换」:外部依赖必须隔离,否则测试又慢又脆。判断口诀:替换的是进程外或不可控依赖;被验证对象内部的一切都走真实路径。