CI运行中自动化Mock回放案例 - 第4节

常用 Mock 工具介绍:选对工具,事半功倍

说实话,刚开始接触 Mock 的时候,我差点被一堆工具搞晕。MockServer、WireMock、VCR、Moto、unittest.mock……每个都说自己能模拟服务,但到底该用哪个?

我在项目里踩过不少坑。有一次用错了工具,Mock 数据写了一大堆,结果 CI 跑起来慢得要命,还经常跟真实的接口冲突。后来才明白,不同场景就得用不同的工具。

嗯,我把常用工具分成了三类:协议级模拟、录制回放、本地轻量 Mock。下面一个一个聊。

MockServer – 通用型 HTTP/HTTPS 模拟

MockServer 是我用得最早的工具。它支持 HTTP、HTTPS、甚至 HTTP/2。你可以在代码里定义期望的请求和响应,然后它就跑起来,像个真服务器一样。

核心特点:

  • 支持动态响应(用 Java 或 JavaScript 写逻辑)
  • 可以配置超时、延迟、异常响应
  • 自带管理 UI,查看请求记录
  • 支持集成测试和独立部署

我个人习惯把 MockServer 用在集成测试环境里。比如服务 A 依赖服务 B,但 B 还没写好,直接用 MockServer 模拟 B 的 API。这样服务 A 的联调就能跑起来。

简单配置示例(Java 客户端):

new MockServerClient("127.0.0.1", 1080)
    .when(
        request()
            .withMethod("GET")
            .withPath("/api/user/123")
    )
    .respond(
        response()
            .withStatusCode(200)
            .withBody("{ \"id\": 123, \"name\": \"Alice\" }")
    );

小技巧:如果只做单元测试,用 MockServer 会有点重。把它用在需要完整 HTTP 协议交互的集成测试场景,效果最好。

WireMock – 灵活又轻量的 Mock 框架

WireMock 跟 MockServer 很像,但我更爱它的声明式配置。它的 DSL 非常清爽,看一眼就能懂。而且它支持独立运行、JUnit 规则、以及 Docker 镜像。

我为什么推荐它:

  • 用 JSON 或 API 定义 stubs,方便版本管理
  • 内置请求匹配器(URL、Header、Body 甚至正则)
  • 支持录制真实请求并回放
  • 性能不错,单机压测都能扛住

举个例子,模拟一个 POST 接口:

stubFor(post(urlEqualTo("/api/order"))
    .withHeader("Content-Type", containing("application/json"))
    .withRequestBody(matchingJsonPath("$.items[0].productId"))
    .willReturn(aResponse()
        .withStatus(201)
        .withHeader("Location", "/api/order/456")
        .withBody("{ \"orderId\": 456 }")));

注意:WireMock 默认把所有请求日志都记在内存里,测试多了可能会 OOM。记得在 teardown 里调用 resetAllRequests() 清一下。

VCR – 录制与回放,像录像机一样

VCR 这个工具很有意思。它把你的 HTTP 请求像录像一样录下来,存成 cassette(磁带)文件,下次再跑同样的请求就直接放录像,不真的去调外网了。

我曾在做支付接口集成测试时用过它。每次跑测试都要真的扣款?不行。用 VCR 录一次,后面就只播放,快又稳。

适用语言版本:

  • Python:vcrpy
  • Ruby:vcr
  • 其他语言也有类似库

核心逻辑:第一次运行,VCR 拦截网络请求,把请求/响应序列化到 YAML 文件。第二次及以后,如果请求匹配 cassette 里的记录,就直接返回缓存的响应。

Python 示例:

import vcr

with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml'):
    response = requests.get('https://api.example.com/data')
    assert response.status_code == 200

避坑指南:我曾经一次把 cassettes 文件提交到 git 后,另一个开发改了接口 URL,结果回放狂失败。后来我们约定:cassettes 只在开发机使用,CI 里强制重新录制,避免过期数据。

Moto – AWS 服务的 Mock 专家

如果你的代码依赖 AWS 服务(S3、SQS、DynamoDB 等),Moto 就是神器。它在内存里模拟 AWS 的行为,不需要真的连云端。

我在做数据管道测试时,S3 操作特别多。用 Moto 后,测试速度从 30 秒降到 1 秒,而且完全离线运行。

支持的服务列表(部分):

服务 模块名 常用场景
S3 mock_s3 文件上传/下载测试
SQS mock_sqs 消息队列测试
DynamoDB mock_dynamodb 数据库读写测试
Lambda mock_lambda 函数调用测试

演示一个 S3 读写测试:

import boto3
from moto import mock_s3

@mock_s3
def test_upload_file():
    client = boto3.client('s3', region_name='us-east-1')
    client.create_bucket(Bucket='test-bucket')
    client.put_object(Bucket='test-bucket', Key='hello.txt', Body=b'world')
    response = client.get_object(Bucket='test-bucket', Key='hello.txt')
    assert response['Body'].read() == b'world'

注意事项:Moto 模拟得再真实,也跟真实 AWS 有细微差别。比如 S3 的事件通知、权限控制等可能不完全一致。最终上线前,建议在真实环境中做一次冒烟测试。

unittest.mock – Python 自带的轻量 Mock

最后这个是 Python 标准库里的 unittest.mock。它不模拟网络协议,但可以替换任何对象和方法。最适合做单元测试里的依赖打桩。

举个例子:你的函数调用了 requests.get(),但不想真的发请求,就用 patch 把它替换掉:

from unittest.mock import patch

def get_data():
    import requests
    response = requests.get('https://api.example.com/data')
    return response.json()

@patch('requests.get')
def test_get_data(mock_get):
    mock_get.return_value.status_code = 200
    mock_get.return_value.json.return_value = {'key': 'value'}
    result = get_data()
    assert result == {'key': 'value'}

适用场景:

  • 测试一个函数内部调用的第三方库方法
  • 模拟数据库查询结果
  • 替换耗时的 IO 操作

个人经验:别把 unittest.mock 用在大范围集成测试里,它的 mock 作用域太小,维护起来容易乱。它最适合精细的单元测试,一个测试只 Mock 一个依赖。

工具选型速览

说了这么多,到底怎么选?我整理了一个对照表,方便你快速决策:

需求 推荐工具 理由
独立部署的 Mock 服务器,支持复杂协议 MockServer / WireMock 配置灵活,支持多种匹配策略
录制回放,减少对外部依赖 VCR 一次录制,多次回放,快且稳定
模拟 AWS 服务 Moto 零网络开销,覆盖大部分 AWS API
单元测试中替换单个方法/对象 unittest.mock 轻量、原生、无额外依赖

记住了:没有万能工具。选哪个取决于你的测试层级和依赖特性。做集成测试用 MockServer/WireMock,做回放用 VCR,做 AWS 模拟用 Moto,做精确单元测试用 unittest.mock。

Mock 工具分类与选型流程 Mock 工具类别与选型指南 协议级模拟 录制回放 本地轻量 Mock MockServer WireMock VCR (vcrpy) unittest.mock Moto 选型建议 • 集成测试/多服务交互 → 协议级模拟 (MockServer / WireMock) • 测试中避免真实外部调用 → 录制回放 (VCR) • 单元测试/替换特定依赖 → 本地轻量 Mock (unittest.mock / Moto)

画了这张图后,你在选工具时应该能快速定位了。我个人还会额外看一个指标:学习成本。团队如果 Python 为主,优先用 unittest.mock + VCR 就够了;如果是 Java 生态,WireMock 值得花时间学一下。

好了,这一章的工具介绍就到这里。记住,Mock 不是目的,让 CI 稳定、快速、可重现才是目标。选错了工具不可怕,多试几次就有感觉了。