Mock回放的基本原理:录制与回放机制

说实话,很多团队刚开始搞Mock回放时,容易把它想复杂了。其实核心就两件事:先录下来,再播回去。听起来像录音笔对吧?嗯,本质逻辑还真差不多。

我在好几年前参与过一个支付核心系统的重构项目。当时要验证新老系统的接口兼容性,但测试环境根本连不上银行网关。怎么办呢?我们就把真实流量录下来,然后在流水线里一遍遍地回放验证。那是我第一次深刻体会到,好的回放技术,比写一千个单元测试都管用。

核心思想一句话:把真实环境中的请求-响应数据保存下来,在隔离环境中重新发送,以此验证代码变更后的行为是否一致。

录制阶段:真实流量的“截图”

录制不是简单的抓包。你想想看,一个请求发出去,可能涉及数据库、缓存、外部API、消息队列……全都要录下来?那成本也太高了。

我个人习惯的做法:只录制外部依赖的进出数据。也就是说,你的业务逻辑内部调用Mock不走录制,只录它出站给其他服务或中间件的调用。

典型的录制工作流是这样的:

  • Hook点:在HTTP客户端、RPC框架或数据库驱动层植入拦截器
  • 上下文绑定:通过Trace ID或Request ID把一次请求的所有对外调用串联起来
  • 序列化存储:把请求参数、响应体、状态码、耗时等关键字段写入文件或数据库
// 伪代码:录制拦截器示意
public class RecordInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 1. 记录请求参数
        RequestRecord req = captureRequest(method, args);
        // 2. 执行真实调用
        Object result = method.invoke(target, args);
        // 3. 记录响应和耗时
        ResponseRecord resp = captureResponse(result);
        // 4. 写入存储
        recorder.save(req, resp);
        return result;
    }
}

小技巧:录制时别忘了带上响应的status code和异常堆栈。我在项目中遇到过回放时明明结果不一致,查了半天才发现是录制时漏了异常场景——那段数据直接没录进去。

回放阶段:重演你的“老剧本”

录制完成之后,就到了重头戏——回放。说白了就是把之前存下来的请求重新发一遍,但这次不是发到真实的外部依赖,而是发给一个Mock Server。

你可能会问:那为什么不直接发到真实服务呢?问题就在这里——真实服务的状态是变化的,数据库里的数据可能已经变了,外部API的限流策略也调整了。一旦发给真实服务,你根本分不清问题是代码改坏了,还是环境变了。

所以回放的核心就是确定性。Mock Server根据录制数据,精确返回当时的结果,保证每一次回放都与录制时的行为完全一致。

典型的回放流程包含这几个环节:

  1. 加载录制数据:从存储中读取录制好的请求-响应映射
  2. 匹配请求:根据请求参数、路径、Header找到对应的录制响应
  3. 返回响应:Mock Server按录制时的原样返回
  4. 差异比对:对比当前被测代码的实际输出与录制时的原始输出
# 配合pytest-mock框架的回放示例
def test_payment_replay(mock_server):
    # 加载录制文件
    recording = load_recording("payment_flow_20240501.json")
    
    # 设置Mock Server回放
    mock_server.setup_replay(recording)
    
    # 触发业务逻辑
    result = payment_service.process(order_id="ORDER_001")
    
    # 验证结果一致
    assert result == recording["expected_output"]

录制与回放的对比一览

维度 录制阶段 回放阶段
执行环境 生产环境/预发环境 CI流水线或测试环境
数据来源 真实用户流量或压测流量 录制文件
依赖状态 依赖真实服务 依赖Mock Server
关注点 数据的完整性和准确性 行为的一致性和差异性
失败影响 可能影响线上服务(需谨慎) 仅影响测试结果,零风险

你可能会踩的坑

我曾经在录制大型分布式系统时,遇到过一个头痛的问题——录制数据里混入了时间戳和随机ID。回放的时候Mock Server按部就班返回同样的响应,但被测代码在验证响应时,发现时间戳对不上,直接抛异常了。

那之后我养成了一个习惯:在录制前先做一次动态字段标记。凡是包含时间戳、UUID、自增ID等字段的,都标记为dynamic,回放比对时自动忽略这些字段。

特别注意:录制数据不要太大。我之前见过一个团队把一整天的全量流量都录进去,结果一个请求就产生了上百MB的录制文件。回放时CI流水线跑了一个多小时……建议只录制核心业务路径和边界场景,控制每次回放的数据量在10MB以内。

另外还有一个容易被忽略的点:录制的顺序性。有些服务依赖请求的先后顺序(比如先创建订单再支付),如果你回放时打乱了顺序,Mock Server返回的数据就会错乱。我个人习惯在录制时带上sequence_id,回放时严格按顺序执行。

Mock回放核心逻辑:录制与回放机制 录制阶段 回放阶段 真实流量请求 外部依赖响应 拦截器录制 → 写入存储 加载录制文件 Mock Server匹配 返回录制响应 → 差异比对 关键原则 录制时保真 → 回放时确定 → 比对时聚焦差异 常见陷阱与应对 • 动态字段(时间戳/ID)→ 标记为 dynamic 字段,比对时忽略 • 录制数据过大 → 只录核心业务路径,单次回放控制在 10MB 以内 • 请求顺序依赖 → 录制时带 sequence_id,回放按序执行

总结我个人的实践心得:Mock回放最基础也最容易被低估的就是录制阶段的数据质量。录得不对,后面所有回放都是白费功夫。我的建议是:先从一个简单的场景起步(比如一次HTTP调用),把录制、存储、回放、比对整个链路跑通,再逐步扩展到复杂场景。别一上来就想录几十个服务,容易把自己绕进去。