「同一测试,本地通过,CI报错,重启解决」——这不是段子,是谷歌工程师的日常。他们最终用「确定性伪造」替换了5万次基于模拟对象的测试,测试套件运行时间砍掉67%。
01 模拟对象的承诺与背叛
测试领域的经典教条说:模拟对象(mock)让测试变快、确定、可靠。用硬编码行为替换不稳定的网络调用,理论上能杜绝随机失败。
谷歌的实践戳破了这个幻觉。
他们发现某个API在代码库中被模拟了数千次。一次API改动,意味着要更新数千处模拟。更致命的是「漂移」——有人改了方法签名,更新部分模拟,漏掉其他。测试开始验证根本不存在的行为。
150多家公司的数据勾勒出残酷图景:
• 开发者31%的时间花在调试失败测试上
• 其中72%最终被证明是「假阳性」——测试错了,代码没错
• 测试通过率与实际生产稳定性的相关性仅31%
「测试通过基本不能说明生产环境能跑通,」一位工程师写道,「这不是测试,是演戏。」
02 模拟死亡螺旋
谷歌内部观察到一个自我强化的恶性循环:
系统演进 → 模拟与现实脱节 → 为边缘情况加更多模拟 → 维护负担爆炸 → 走捷径「先让它绿」→ 准确性崩塌 → 测试漏掉更多Bug → 到处加防御性代码和重试逻辑 → 需要更多模拟处理防御情况……
他们称之为「模拟死亡螺旋」。一旦陷入,几乎无法脱身。
某团队「快速」单元测试套件跑到45分钟。不是测试变复杂了,是重试逻辑堆成了山。开发者彻底不信任结果:绿的?「真过了吗?」红的?「估计又抽风,合了吧。」
当测试从安全网退化成可选建议,根基就烂了。
03 确定性伪造:不是更好模拟,是不同物种
谷歌的解法是「确定性伪造」(deterministic fakes)。关键区别:模拟对象验证「被测代码如何调用依赖」,伪造则提供「行为正确但简化版的依赖实现」。
具体做法:
• 为关键依赖构建轻量级内存实现
• 这些实现保持与生产代码一致的行为契约
• 不验证调用顺序/次数,只验证最终状态正确
结果是平滑、可预测的测试执行。没有随机超时,没有「本地绿CI红」的玄学。
5万次测试重写后,测试套件运行时间从基准线砍掉67%。更隐蔽的收益:开发者重新相信测试结果。
04 为什么这事现在值得重提
微服务架构让这个问题更痛。服务边界越多,需要模拟的外部依赖越多,螺旋转速越快。
谷歌的实验暗示一个反直觉结论:测试速度未必来自「隔离」,而来自「可控的确定性」。伪造比模拟更重(毕竟要维护简化实现),但省下的调试时间和心理成本,在规模下完全值得。
这不是说模拟对象该被抛弃。边界清晰的纯函数、第三方SDK,模拟仍有效。但当模拟数量以千计时,维护成本曲线会击穿收益。
多数团队没谷歌的工程资源做5万次迁移。但识别自己是否在螺旋中,是零成本的第一步。你的测试失败里,有多少比例最终被证明是测试本身的问题?如果超过三成,可能已经在下滑轨道上了。
热门跟贴