mcpField
S
service-virtualization

service-virtualization

github.com/petrkindlmann测试与质量2221.0k 次安装6 天前更新
代码仓库

关于 service-virtualization

一项帮助测试团队为依赖选择隔离策略的技能,涵盖 HTTP 桩、录制回放、故障注入和临时真实服务。它强调在 HTTP 层设置桩,并检查测试是否拦截了未匹配的请求。

主要功能

  1. 01按依赖和测试层级选择隔离方式
  2. 02在 HTTP 层而非 SDK 方法设桩
  3. 03使用 MSW 和 WireMock 模拟 HTTP
  4. 04通过 Testcontainers 启动真实服务
  5. 05用 Toxiproxy 注入网络故障
  6. 06录制并回放 API 流量

适用场景

  1. 01无需调用线上服务即可测试第三方 API 错误
  2. 02使用临时数据库运行集成测试
  3. 03验证超时和连接重置的处理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petrkindlmann/qa-skills --skill service-virtualization

安装后在 Claude Code 中输入/service-virtualization确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ci.md
msw.md
record-replay.md
testcontainers.md
toxiproxy.md
wiremock.md

详细介绍

在 SDK 层模拟 Stripe,测试全部通过;但 Stripe 一旦更改响应字段,生产环境就会立即返回 500——因为存根从未与契约关联。这项技能会针对每个依赖选择合适的隔离策略(进程内模拟、HTTP 存根、录制回放、故障注入或临时真实服务),在 HTTP 层设置存根,让测试不受 SDK 升级影响,并确保 CI 在任何真实调用绕过存根时失败。

快速导航

情况前往
Node/浏览器测试调用外部 HTTP APIMSW → references/msw.md
多语言 CI、复杂匹配,或需要独立的存根服务WireMock → references/wiremock.md
依赖是数据库 / 缓存 / 队列Testcontainers → references/testcontainers.md
测试超时、延迟、连接重置Toxiproxy → references/toxiproxy.md
从真实 API 初始化存根,或建立多步骤基线录制回放 → references/record-replay.md
将上述任一工具接入 GitHub Actionsreferences/ci.md
不确定该选哪种决策树(下文)

调研问题

先检查 .agents/qa-project-context.md——如果存在,请使用其中的信息,遵循现有的模拟约定,并跳过其中已解答的问题。然后:

  • 系统会调用多少个外部依赖?哪些依赖在测试中很难处理? 有速率限制、速度慢、不稳定或需要付费的依赖,是最优先考虑虚拟化的对象;列出支付、电子邮件、身份验证和第三方数据源。
  • 哪些测试层级需要隔离? 单元测试需要快速的进程内模拟;集成测试需要 HTTP 层存根;端到端测试需要真实服务或容器化服务——测试层级决定策略。
  • 提供方是否提供官方测试模式或沙箱? Stripe 测试模式、Twilio 测试凭证等,在保真度方面都胜过自建存根——有此类选项时优先使用。
  • 你是否与这些依赖建立了契约? 如果有,契约测试可以让存根保持同步;请参阅 contract-testing。如果没有,模拟非自有对象是一项已知风险(核心原则 3)。
  • CI 中是否有 Docker? 没有 Docker 时应选择 MSW(进程内);有 Docker 则可以使用 WireMock、Testcontainers 和 Toxiproxy。

核心原则

1. 根据所需的置信度选择隔离级别。 单元测试可以积极地使用模拟——它们测试的是内部逻辑。集成测试应使用逼真的存根或真实服务,因为它们测试的是边界。端到端测试应运行尽可能接近生产环境且仍然可靠的服务。

2. 真实服务只要速度快、免费且可靠,就胜过伪造服务。 本地 PostgreSQL 容器比伪造服务更让人放心,成本也很低——应优先使用。只有对速度慢、不可靠或需要付费的依赖,才使用伪造服务。

3. 没有契约时,绝不要模拟非自有对象——并优先使用提供方的测试模式。 如果你为 Stripe 设置了存根,而 Stripe 更改了响应结构,你的测试仍会通过,生产环境却会出问题。按优先顺序:使用提供方的官方测试模式 / 沙箱(Stripe 测试模式、Twilio 测试凭证)→ 使用通过契约测试验证漂移的 HTTP 存根 → 仅在没有更好选择时使用裸存根。请参阅 contract-testing。

4. 存根必须逼真地失败。 始终返回 200 的存根无法测试错误处理。每个存根都需要错误变体:429 速率限制、500 错误、超时、格式错误的响应正文。

5. 在测试和工具之间只保留一层抽象。 将 MSW 处理器、WireMock 存根和 Testcontainers 配置封装在统一接口后,这样切换工具就不必重写测试。


决策框架

何时使用各隔离策略
策略速度保真度复杂度最适合
进程内模拟最快最低极低单元测试、隔离内部模块
HTTP 桩(MSW)快中等低调用外部 API 的前端/Node 测试
HTTP 桩(WireMock)快中高中等与语言无关的场景、复杂匹配规则
录制回放首次运行后很快初始较高,之后会衰减中等快速根据真实 API 搭建桩
服务伪实现中等高高有状态依赖(内存数据库、伪认证)
临时真实服务(Testcontainers)较慢最高中等数据库、消息队列、缓存
共享真实服务慢生产级低(搭建成本)预发布验证、部署前最终检查
决策树
Is the dependency internal to your codebase?
├─ Yes → In-process mock (vi.mock / jest.mock / monkeypatch)
└─ No → Is it a database, cache, or message queue?
         ├─ Yes → Testcontainers (ephemeral real instance)
         └─ No → Is it a third-party HTTP API?
                  ├─ Yes → Does the provider offer a test/sandbox mode?
                  │        ├─ Yes → Use sandbox in staging, MSW/WireMock in CI
                  │        └─ No → MSW or WireMock + contract test for drift detection
                  └─ No → Is it an internal microservice?
                           ├─ Yes → Contract test (Pact) + stub for consumer tests
                           └─ No → Evaluate case by case

工具

进程内 Node/浏览器测试默认选择 MSW;跨语言 CI 或独立桩服务器选择 WireMock;以 OpenAPI 规范作为契约时选择 Prism;开发时探索性模拟则选择 Mockoon。各工具的大段代码位于 references/ 中,下面提供相关指引。

MSW(Mock Service Worker)

MSW 可在浏览器或 Node 中拦截 HTTP。本参考针对固定的 2.15.0 API;复制代码前请检查已安装的主版本,因为 MSW 3 会更改导入项和选项。 在 HTTP 层搭建桩(POST /v1/payment_intents),不要模拟 SDK 方法——这样可让测试与 SDK 版本解耦。

对于 MSW 2,使用 setupServer(...).listen({ onUnhandledRequest: "error" }),拒绝没有匹配处理程序的拦截请求。验证预期的模拟响应,并验证一次有意触发的未处理请求拒绝。显式透传、未捕获的传输,或吞掉该拒绝的应用代码,都需要单独检查;测试套件通过或日志搜索结果为空,并不能保证没有出站请求。

将选择启用的错误处理程序放在匹配的成功处理程序之前。在测试之间重新构建有状态工厂:单独调用 resetHandlers() 不会清除工厂捕获的映射。有关支付处理程序、显式本地回退、状态重置、包含超时/重试用例的可执行示例客户端,以及基于 Bearer 令牌的有状态认证流程(创建/验证/刷新/通过 http.delete 撤销),请参见 references/msw.md。

WireMock

与语言无关的 HTTP 桩服务器(当前稳定版为 3.13.2——截至 2026 年年中,4.0 仅为 Beta 版;CI 请继续使用 3.x)。可独立运行,也可作为 Docker 容器运行。最适合多语言环境或复杂匹配规则。

使用基于优先级的映射处理错误场景:只有测试明确要求时,匹配选择性启用请求头(X-Test-Scenario: rate-limit)并返回 429 的 priority: 1 映射才会覆盖默认的正常路径映射。WireMock 还提供管理 API,可用于以编程方式创建桩(POST /__admin/mappings)、验证(POST /__admin/requests/count)和重置(POST /__admin/mappings/reset)。

有关 Docker 设置、使用响应模板的分页映射、优先级错误映射 JSON,以及连接到 contract-testing 的差异检测接口(回放 Pact 或通过 Prism 根据 OpenAPI 规范验证桩),请参见 references/wiremock.md。

其他 HTTP 模拟服务器
工具优势使用场景
Mockoon桌面界面 + CLI;导入 OpenAPI;基于规则的响应;轻量开发时模拟,以及在 CI 中快速创建 CLI 模拟
Hoverfly捕获回放(录制真实流量并确定性地回放);捕获/模拟/修改/合成模式从真实依赖迁移到模拟——录制一次,永久回放
PrismOpenAPI 驱动的模拟服务器(Stoplight);验证请求 + 根据规范生成响应优先采用 OpenAPI 且已发布规范的项目
MockServer基于 Java;支持丰富的期望匹配;多协议已在使用 MockServer 的 JVM 团队
Testcontainers

在 Docker 中启动真实服务以进行集成测试——测试套件运行前启动容器,结束后销毁容器。使用 @testcontainers/postgresql 11.x 及相关工具。端口是随机分配的;始终通过 getMappedPort() / getConnectionUri() 读取端口,绝不要硬编码 5432/6379。

请参阅 references/testcontainers.md,了解使用等待策略并行启动 PostgreSQL + Redis + Elasticsearch、Vitest 的 globalSetup 配置(process.env.DATABASE_URL、testTimeout: 30_000),以及更新过时镜像标签的说明(elasticsearch:8.12.0 已落后于 Elastic 9.x)。

Toxiproxy(故障注入)

ghcr.io/shopify/toxiproxy:2.12.0 作为 TCP 代理置于应用与依赖项之间,可注入延迟、带宽限制和连接重置。让应用连接到代理端口,而非真实服务。务必在 afterEach 中移除 toxics,否则它们会影响后续测试。

请参阅 references/toxiproxy.md,了解 compose 端口映射、createProxy(name, listen, upstream) 配置(包括 compose 的 15432/16379 代理端口如何映射到真实上游服务)、每次调用都检查 response.ok 的辅助函数,以及延迟 + 连接重置用法示例。若要开展大范围的韧性测试或游戏日演练,请改用 chaos-engineering。


录制-回放

录制-回放会捕获真实 API 响应,并以确定性的方式重放——适合用于搭建存根,也适合作为多步骤交互的回归基线。请使用录制-回放库(Hoverfly、Polly.JS 或 VCR 风格的录制文件)来实现,而不要自行编写录制器。

动态数据(时间戳、UUID)、有状态序列和时间推移都会导致它失效——录制内容可能在几周内就会过时。务必记录 recordedAt,并在录制内容超过 30 天时让测试失败,以强制重新录制。

请参阅 references/record-replay.md,了解录制文件格式、assertFresh() 的 30 天过期检查,以及驱动多步骤流程(创建订单 → 添加商品 → 应用优惠券 → 结账)的回放测试框架。


CI 集成

MSW 不需要单独的存根服务器。在 CI 中设置 onUnhandledRequest: "error",断言预期的模拟响应已被观察到,并执行一个刻意未处理的请求来验证防护机制。记录意外绕过的请求;若要求完全禁止出站网络流量,请使用 CI 网络控制。WireMock 和 Testcontainers 需要 Docker。

两种不兼容的端口模型并存,不得在同一测试套件中混用:docker-compose 模型会发布固定端口(硬编码 DATABASE_URL=...localhost:5432... 可行);Testcontainers 模型使用通过 getMappedPort() 读取并注入 process.env 的随机端口。每个测试套件只能选用一种模型。

请参阅 references/ci.md,了解 MSW 步骤、docker-compose GitHub Actions 任务(up -d --wait --wait-timeout 120、if: always() 清理),以及按约束条件分类的工具表。


反模式

1. 什么都模拟

如果每个依赖项都被模拟,你的测试验证的只是模拟本身是否有效,而非系统是否有效。数据库和缓存请使用真实服务(通过 Testcontainers);仅对外部 HTTP API 使用存根。

2. 不同测试中的模拟行为不一致

一个测试将 Stripe 存根为 { id: "pi_123" },另一个则存根为 { paymentIntentId: "pi_123" }——现在你有了两个互相冲突的现实版本。请集中管理处理程序,并在整个测试套件中复用同一种响应结构(参见 references/msw.md 中的共享结构)。

3. API 变更后未更新存根

你的映射表明 Stripe 返回 { amount: 1000 },但真实 API 现在返回 { amount: 1000, currency: "usd" }。测试通过了,生产环境却失败。请使用契约测试检测偏差——参见 contract-testing 和 references/wiremock.md 中的偏差检测接口。

4. 在错误的层进行存根

模拟 stripe.paymentIntents.create(SDK 方法)会使测试与 SDK 版本耦合。请在 HTTP 层设置存根(POST /v1/payment_intents),这样无论使用哪个 HTTP 客户端或 SDK 版本,测试都能正常运行。

5. 未覆盖错误场景

始终返回 200 的桩不会触发重试逻辑、超时处理、限流退避或错误解析。每个桩都需要对应的错误变体。

6. 共享的长期运行模拟服务

多个 CI 作业共同访问同一个 WireMock 实例会造成耦合和状态泄漏。每次测试运行都要启动自己的隔离桩服务。

7. 未设置过期时间的录制回放

六个月前的录制反映的是一个已不存在的 API。添加 recordedAt 时间戳,并在录制超过 30 天时让测试失败,强制重新录制(参见 references/record-replay.md)。

8. 成功处理器遮蔽错误场景

MSW 会在遇到第一个返回响应的匹配处理器时停止。放在创建支付处理器之后的限流处理器永远无法返回 429。将选择性启用的错误处理器放在前面,然后针对同一端点验证错误请求和普通成功请求。

9. 重置处理器但保留夹具状态

server.resetHandlers() 会移除运行时覆盖项,但现有闭包仍持有支付或会话映射。每次测试之间都要重新创建各个有状态处理器工厂,并验证之前存在的记录之后已不存在。在夹具中使用唯一 ID,而不是在冻结时钟下会发生冲突的时间戳。


验证

验证实际代码和观察到的请求,从最小检查开始:

# In this skill repository: execute the actual MSW reference code fences.
node --test --test-isolation=none tests/examples/msw.test.mjs

回归测试必须观察到正常的创建/获取/确认流程、选择性启用的 429、处理器已收到请求后客户端发生超时、次数受限的 503 重试、重置后的新状态,以及在回退之前被拒绝的刻意未处理请求。将限流处理器移到成功处理器之后,或保留旧状态,都必须使相应案例失败。

对于使用者项目,在 CI 设置下运行其实际集成命令,并验证相同信号。日志中未出现提供方主机名,只能证明搜索结果中没有对应行;模拟请求本身也可能记录提供方 URL。请分别检查匹配/未处理/绕过请求的证据和网络限制情况。对于容器夹具,请验证其正常启动并确保完成清理。说明实际测试过哪些传输方式和场景;此配方测试不验证真实提供方契约。


完成标准

  • 已为每个外部依赖确定并记录隔离策略(哪些使用 MSW/WireMock 桩,哪些使用 Testcontainers,哪些使用提供方的沙盒模式)。
  • 桩覆盖所有关键外部依赖,且每个依赖至少包含一条错误路径(4xx/5xx、超时或限流)。
  • 桩和映射文件与测试代码一同在同一个代码仓库中进行版本控制。
  • 在已安装框架的未处理请求保护机制启用时,测试套件运行通过;观察到预期的模拟请求,且刻意未处理的请求证明保护机制会拒绝回退。未预期的绕过均已说明;零出网声明有网络证据支持。
  • 有状态夹具和运行时覆盖项会在测试之间重置;重置前创建的记录之后已不存在,之前的错误覆盖项也不再影响下一项测试。
  • 任何录制回放基线都带有 recordedAt 时间戳和 30 天过期检查,过期时会使测试失败。

参考文件(位于 references/ 中)

  • msw.md — 有序的支付/错误处理程序、带显式本地回退开关和状态重置的 Vitest 设置、可执行的超时/重试示例,以及创建/验证/刷新/撤销身份验证。
  • wiremock.md — Docker 设置、响应模板化和分页映射、基于优先级的错误映射、管理 API,以及契约漂移衔接点(Pact/Prism)。
  • testcontainers.md — 并行启动 PostgreSQL/Redis/Elasticsearch、globalSetup 接线,以及镜像标签刷新说明。
  • toxiproxy.md — compose 端口、createProxy 上游接线、带 response.ok 检查的辅助函数,以及延迟 + 重置用法示例。
  • record-replay.md — 工具选择、录制带格式、30 天 assertFresh 检查,以及多步骤回放测试框架。
  • ci.md — MSW(零基础设施)、docker-compose GitHub Actions 作业、两种端口模型,以及按约束条件划分的工具表。

相关技能

  • contract-testing — 使用 Pact/broker 进行消费者驱动的契约验证;若要证明桩与真实提供方匹配,而不只是检测它与共享架构之间的漂移,请参阅此项。
  • test-environments — 完整的 Docker Compose 环境策略、预览环境和种子数据;若要搭建环境,而非隔离单个依赖项,请参阅此项。
  • chaos-engineering — 广泛的故障注入演练、演练日和爆炸半径限制;若目标是韧性本身,而非让单个依赖项在测试中表现异常,请参阅此项。
  • api-testing — REST/GraphQL 测试模式、架构验证和身份验证流程测试。
  • test-data-management — 用于设置桩状态的工厂模式和数据播种。

评论

支持 Markdown