第1章 引言:什么是Mock回放?为什么在CI中重要?

咱们先从一个实际问题聊起。

你在 CI 里跑集成测试,结果总被外部服务搞砸。第三方 API 限流了,数据库连接超时了,消息队列挂了。测试挂了,但完全不是你代码的问题。

是不是很头疼?

我在很多项目里都碰到过这种情况。以前有个项目,每次发版前跑 CI,大概要花四十分钟。其实代码逻辑没问题,但依赖的外部服务总是不稳定。最后我实在受不了,引入了 Mock 回放。结果呢?四十分钟缩短到八分钟,而且再也没有因为外部服务不稳定而失败过。

这章我们就来聊聊这个技巧。

1.1 Mock 回放是什么?

说白了,Mock 回放就是把真实服务的响应“录”下来,然后在测试中“播”出去。

你想想看,传统 Mock 是手写的假数据。写死了返回值,根本不管真实服务的行为。比如你 mock 了某个接口永远返回 200,但真实场景下它可能返回 301 重定向,或者 500 错误。这样的测试,能算有效吗?

核心区别:

  • 传统 Mock: 人为构造假响应,假设服务行为。
  • Mock 回放: 录制真实响应,还原真实服务行为。

我的习惯是,优先用 Mock 回放。为什么?因为它接近真实。录下来的数据里,包含真实的响应状态码、延迟、甚至是一些边界值。

1.2 为什么 CI 里需要它?

CI 的核心诉求是什么?稳定、快速、可复现。

但外部依赖常常破坏这三条。

痛点 传统做法的问题 Mock 回放的价值
稳定性 外部服务宕机,CI 全红 不依赖外部,CI 只认录好的数据
速度 等待真实接口响应,可能耗时数秒 本地回放,毫秒级响应
可复现 每次请求可能返回不同结果 相同请求,永远返回相同响应

提示:我个人建议,把 Mock 回放看作是对真实服务的“快照”。锁定了这个快照,你的测试就有了确定性。

1.3 一个简单的例子

假设你有一个服务,需要调用外部天气 API 来获取数据。传统做法是写一个 Mock:

// 传统 Mock
mockWeatherAPI = () => {
  return { temperature: 25, condition: "sunny" }
}

这个 Mock 永远返回晴天。但真实 API 可能返回雨天、雪天,甚至是限流提示。用 Mock 回放的话,你先去真实 API 上跑一次请求,把响应录下来:

# 录制
curl -X GET "https://api.weather.com/v1/current" -o weather_response.json

然后在测试中,用录制好的文件代替真实请求:

// Mock 回放
const response = readFile("weather_response.json")
return JSON.parse(response)

嗯,这里要注意。录下来的响应要定期更新。我在项目中一般会设置一个定时任务,每周六凌晨重新录制一次。确保数据不会太老。

1.4 本章知识体系

下面这张图,可以帮助你快速理解 Mock 回放在 CI 流水线中的位置和流程。

Mock回放在CI流水线中的位置 代码提交 构建 单元测试 Mock回放测试 (核心:不依赖外部服务) 集成测试 录制真实响应 测试通过后清理 Mock 数据

图中的重点,就是中间那个紫色节点。它把测试从外部依赖中解放了出来。

1.5 常见的陷阱

当然,Mock 回放也不是银弹。我曾经踩过不少坑。

避坑指南:

  • 录制的响应太旧: 我曾经因为一个月没更新 Mock 数据,导致测试代码里逻辑正确,但上线后因为真实 API 字段变化直接报错。所以,录制数据的定期刷新很重要。
  • 忽略响应延迟: 录下来的响应是毫秒级,但真实服务可能有几百毫秒延迟。我建议在回放时加入一个可配置的延时参数,模拟真实网络环境。
  • 只录成功场景: 很多人只录 200 成功响应。但我习惯把 4xx、5xx 也录下来,用来测试错误处理逻辑。

你想想看,如果你的测试中只覆盖了“晴天”路径,那“下雨”时程序崩了,你能提前发现吗?

嗯,这就是 Mock 回放的价值。它让你的测试更接近真实世界,同时又不依赖真实世界。

一个实用技巧:我通常会在 CI 里设置两个阶段。第一阶段用 Mock 回放快速验证逻辑。第二阶段再跑少量、必要的集成测试验证连通性。这样既快又稳。