Mock 概念与为什么需要 Mock 回放
说个实在的,不管你是做微服务还是单体应用,只要涉及外部依赖,就一定逃不过 Mock。
Mock 到底是啥?说白了,它就是帮你「伪造」一个服务。比如你的代码要调支付接口,总不能每次跑测试都真扣钱吧?Mock 就是那个替身,返回你想要的结果。
Mock 的基础概念:替身演员
我习惯把 Mock 理解成「测试界的替身演员」。正主(真实服务)太金贵或者太慢,就找个替身来搭戏。这个替身必须具备两个特点:
- 返回值可控:你让它返回成功,它就返回成功;你让它抛异常,它就抛异常。
- 零副作用:调用 Mock 不会真的扣钱、不会真的发邮件、不会真的写数据库。
举个例子:
// 真实支付服务
public interface PaymentService {
boolean charge(String userId, double amount);
}
// Mock 版本 —— 你希望它总是返回 true
PaymentService mockService = Mockito.mock(PaymentService.class);
when(mockService.charge("test_user", 100.0)).thenReturn(true);
这段代码里,mockService 根本不会去调第三方支付网关,它只是乖乖返回 true。嗯,这就是 Mock 最朴素的功能。
为什么需要 Mock?三个核心痛点
我在项目中遇到过不少团队,刚开始觉得 Mock 能省事,结果越用越乱。我总结了三个必须用 Mock 的场景:
- 依赖不可用:测试环境里,数据库、缓存、消息队列经常不在线。Mock 帮你把它们「虚拟化」。
- 测试速度太慢:一个接口调远程服务要打几个来回,网络延迟加起来好几秒。Mock 直接把延迟砍到微秒级。
- 模拟异常场景:你真的指望生产环境给你来一次断网?Mock 可以轻松模拟超时、500 错误、空指针。
注意:Mock 不是越大越好。过度 Mock 会导致测试与真实行为脱节,后面你会看到 Mock 回放正是来解决这个问题的。
Mock 的局限性:为什么需要回放?
你想想看,传统的 Mock 有一个致命问题——它跟真实行为脱钩。什么意思呢?
比如你 Mock 了支付接口返回 true,但如果真实支付接口某天改了返回格式,你的 Mock 是不知道的。测试照样全绿,上线就炸。
我之前就吃过这个亏。有一次对接的第三方订单系统升级了接口,我这边 Mock 里的字段名没跟上。CI 跑得挺欢,一上线就报「字段不存在」。排查了半天,最后发现是 Mock 和真实接口对不上了。
这就引出了 Mock 回放的必要性。回放机制的核心思路是:
- 录制:先对着真实服务跑一次,把请求和响应录下来。
- 回放:下次跑测试时,直接拿录制的内容来 Mock,
- 校验:如果真实服务变了,回放时会自动检测不匹配。
一句话总结:传统 Mock 是「猜」答案,Mock 回放是「抄」答案,而且每次 CI 都要保证你抄的答案还是对的。
Mock 在 CI 流程中的位置
我习惯用下面这个图来说明 Mock 回放在整个 CI 流水线中的角色。说真的,它卡在「单元测试」和「集成测试」之间,既保证了速度,又不失可靠性。
什么时候用传统 Mock,什么时候用回放?
我习惯按稳定性来区分。话说回来,也不是所有场景都需要回放。下面这张表是我在实际项目中总结的经验:
| 场景 | 传统 Mock | Mock 回放 |
|---|---|---|
| 第三方支付接口 | ❌ 不可靠,易变 | ✅ 推荐 |
| 内部基础服务(如鉴权) | ✅ 相对稳定 | 可选 |
| 数据库查询 | ✅ 常用 | ❌ 性价比低 |
| 消息队列 | ❌ 异步难模拟 | ✅ 推荐 |
| 遗留系统(无人维护) | ❌ 不靠谱 | ✅ 必须用 |
我的习惯:对于「别人维护、我不改、但常出问题」的依赖,一律上 Mock 回放。对于「我自己维护、代码可见」的内部模块,用传统 Mock 就够了。
一点个人体会
曾经有一个项目,我们团队对每个外部依赖都写了传统 Mock,CI 跑得飞快,一次都没出过错。结果上线第一天,订单模块直接瘫痪。原因?Mock 返回的字段名和真实接口不一致,前端拿不到数据。
从那以后,我立了一个规矩:凡是外部依赖的测试,必须从真实录制开始。Mock 回放不是给自己找麻烦,而是给 CI 上了一道保险。
说白了,Mock 回放就是把「我以为它没问题」变成「它真的没问题」。
核心观点:Mock 是 CI 提速的手段,回放是 CI 保质的底线。两者结合,才能在快速迭代中不翻车。