Mock与Fake的核心区别:验证方式(期望 vs 结果)、实现代价、维护成本、适用阶段

虽然 Mock 和 Fake 都用来模拟依赖组件,但它们的设计目标和核心契约存在根本差异。理解这些差异是测试架构决策的关键:选错角色会导致测试脆弱、维护成本激增,甚至失去测试的有效性。以下从四个维度展开对比。

1. 验证方式:行为期望 vs 状态结果

这是最根本的区别。Mock 关注交互过程,Fake 关注最终结果。

  • Mock(交互验证):验证被测试代码是否以期望的方式调用了依赖组件(例如:调用了某方法、传入了特定参数)。它通过设置期望(Expectations)和后续验证(Verify)来工作。
  • Fake(状态验证):提供依赖组件的轻量级功能实现,测试只验证最终返回的结果或系统状态是否正确,完全不关心内部调用过程。
🎯 快速类比:
Mock = 收银员:收银员关注过程——你是否按正确步骤结账、是否出示了优惠券。一旦步骤错误,立刻干预。
Fake = 计算器:计算器只关注结果——你输入数字和运算符,它给出答案。它完全不关心你按按钮的顺序是否正确。

在代码层面,这不是直接依赖返回值,而是依赖行为记录:

// Mock 风格(交互验证)
mockService.doSomething("param");
verify(mockService).doSomething("param"); // 验证是否发生了此交互

// Fake 风格(状态验证)
fakeService.addData("key", "value");
assertEquals("value", fakeService.getData("key")); // 只验证结果状态

2. 实现代价

维度 Mock Fake
实现复杂度 低——通常利用 Mock 框架(如 Mockito)动态生成 高——需要手写或维护一套轻量级的功能逻辑
代码量 极少——几行声明和验证语句 较多——需要实现接口的所有方法,包括边缘情况
初始化成本 极低——无状态,每次调用可即时生成 中等——需要预设数据或维护内部存储结构

结论:Mock 在编写阶段更"廉价",尤其适合快速隔离测试;Fake 前期建设成本更高,但具有长期价值。

3. 维护成本

这是被误解最深的维度。许多人认为 Mock 更易维护,但实际情况往往相反:

  • Mock 维护成本高(脆弱性):Mock 与被测代码的行为强耦合。只要被测试代码修改了内部调用细节(即使外部行为不变),Mock 期望就会失效,导致测试红色失败。这种"脆弱测试"会严重消耗团队维护精力。
  • Fake 维护成本低(健壮性):Fake 与接口契约耦合。只要接口签名和业务语义不变,即使内部调用逻辑重构,Fake 和测试代码都无需修改。它只关心"结果对不对",不关心"过程怎么走"。
⚠️ 常见误区: "使用 Mock 能减少维护工作"——错! 过度使用 Mock 进行行为验证会创建大量脆弱测试。每次代码重构(即使不改变外部行为)都需要同步更新 Mock 期望。相比之下,基于 Fake 的状态验证测试往往能通过重构而毫发无损。

4. 适用阶段

  • Mock → 单元测试阶段:当需要完全隔离被测代码,验证其与其他组件之间的协作契约是否被严格遵守时,Mock 是唯一选择。典型场景:验证第三方 SDK 调用参数、验证回调是否触发。
  • Fake → 集成测试/功能测试阶段:当需要验证数据流和业务逻辑是否正确,且不希望依赖真实基础设施(如数据库、外部 API)时,Fake 是最佳选择。典型场景:使用内存数据库 Fake 代替真实数据库运行全套仓储测试。

核心区别总结

以下 SVG 结构图直观展示了 Mock 与 Fake 在上述四个维度的对比:

Mock vs Fake 核心区别矩阵 Mock(模拟对象) Fake(伪造对象) 验证: 期望 (行为) 验证: 结果 (状态) vs 实现代价: 低 (轻量) 实现代价: 高 (重量级) vs 维护成本: 高 (脆弱) 维护成本: 低 (健壮) vs 适用阶段: 单元测试 适用阶段: 集成/功能测试 vs 核心选择依据 需要验证交互过程(Mock)还是只需要验证最终结果(Fake)?

常见误区

  1. "Mock 和 Fake 可以完全互换" —— 不可以。它们在验证哲学和适用阶段上有本质冲突,生硬互换将导致测试失去焦点或成本失控。
  2. "Fake 就是复杂的 Mock" —— Fake 是功能实现,不涉及行为验证;Mock 是交互记录器,不提供真实功能。两者概念平行,不存在包含关系。
  3. "使用 Mock 框架就不需要手写 Fake" —— Mock 框架只能生成 Mock,无法生成具备真实业务语义的 Fake。当你需要模拟数据库行为时,必须手写 Fake。

练习题

判断以下场景应使用 Mock 还是 Fake,并说明理由。

  1. 场景 A:测试一个支付网关包装器,需要验证是否向网关发送了正确的加密签名参数,且只关心签名参数是否正确,不关心网关是否真正处理了交易。
  2. 场景 B:测试一个用户仓储模块,需要确保其 CRUD 操作的正确性。你希望测试能独立于真实数据库运行,且只关心数据的存取结果是否正确。
💡 参考答案:
场景 A → Mock:核心关注点是调用参数,属于交互行为验证,必须使用 Mock。
场景 B → Fake:核心关注点是数据存取状态,且需要模拟完整功能,最适合手写一个基于 HashMap 的内存 Fake 实现。

应用边界

  • Mock 的边界:不要对简单数据容器或不涉及外部协作的内部查询使用 Mock。这会引入无意义的脆弱性。Mock 只用于验证跨组件边界的交互契约。
  • Fake 的边界:不要为无状态纯函数或一次性计算逻辑构建 Fake。Fake 的维护成本在此时是多余的。Fake 只用于提供需要长期维护的、有状态的依赖替代品(如数据库、文件系统、第三方服务 SDK)。
  • 混合使用策略:在同一项目中,两者可以共存。例如:在单元测试层使用 Mock 隔离外部依赖,在集成测试层使用 Fake 验证数据流。关键在于清晰区分各自的职责边界。