mcpField
C
contract-testing

contract-testing

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

关于 contract-testing

面向采用 Pact 的团队,指导构建消费者驱动的契约测试流程,覆盖消费者测试、提供方验证和部署门禁。通过 Pact Broker 与 can-i-deploy 检查兼容性,帮助服务独立发布。

主要功能

  1. 01生成 Pact 消费者测试
  2. 02通过 Broker 验证提供方
  3. 03使用 can-i-deploy 阻止不兼容部署
  4. 04支持 HTTP 与消息契约
  5. 05提供 OpenAPI 与 Pact 混合方案

适用场景

  1. 01在 CI 中发现接口字段变更造成的兼容问题
  2. 02在没有共享集成环境时独立部署服务
  3. 03为 Kafka 或队列消息验证契约

安装

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

$npx skills add https://github.com/petrkindlmann/qa-skills --skill contract-testing

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

包含的文件

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

references/
ci-pipelines.md
pact-js-setup.md
schema-first.md

详细介绍

某个提供方重命名了一个响应字段。两个服务都通过了各自的单元测试,但直到生产环境中消费者的前端出错时,才暴露出不匹配问题。契约测试可在 CI 中捕获此类问题:消费者明确声明所需内容,提供方验证自己能够提供,而在 broker 确认双方兼容之前,`can-i-deploy` 会阻止部署。这项技能可生成 Pact 消费者测试、由 broker 驱动的提供方验证,以及将两者联系起来的部署门禁,让服务无需共享集成环境即可独立部署。

发现问题

先检查 .agents/qa-project-context.md——如果文件存在,就使用其中的信息,并跳过已回答的问题。然后:

  1. 架构: 微服务、带有独立消费者(移动端/SPA)的单体,还是 BFF 模式?团队独立部署时,契约测试最为重要。
  2. 谁拥有契约? 消费者驱动(由消费者定义所需内容),还是提供方驱动(由提供方发布规范)?大多数团队采用消费者驱动的方式会受益。
  3. API 版本策略: 基于 URL(/v1/、/v2/)、基于请求头,还是没有版本策略?契约必须考虑版本协商。
  4. 有多少组消费者—提供方配对? 从流量最高或最脆弱的集成开始。不要试图一次性为所有内容编写契约测试。
  5. 现有 API 规范: 是否有 OpenAPI/Swagger 规范?如果有,可以考虑从模式优先的契约(或 Schemathesis)入手。
  6. HTTP 还是异步? 请求/响应 API 使用此处的 HTTP 示例;队列/主题集成(Kafka、SNS/SQS)使用 Pact 消息契约(参见 Pact-JS Setup)。

核心原则

1. 消费者定义所需内容,提供方验证自己能够提供。 消费者编写测试,声明“我将调用 GET /users/123,并期望收到 { id, name, email }。”提供方针对其实际实现运行此测试。如果提供方无法满足契约,构建会在部署前失败。

2. broker 是共享的事实来源。 不是文档,不是 Slack 讨论串,也不是“直接部署看看”。Pact 和验证结果都存放在 Pact Broker 中;提供方验证会从 broker 拉取 Pact(pactBrokerUrl + consumerVersionSelectors),而不是从本地文件读取,因为 broker 知道哪些消费者版本实际上正在运行。

3. 契约测试取代的是集成环境,而不是集成测试。 对于复杂的多步骤工作流,你仍然需要集成测试。契约测试则免去了仅为验证接口而同时部署消费者和提供方的需要。

4. 契约违规时应让构建失败。 只记录警告却仍允许部署的契约测试毫无价值。契约必须作为部署门禁。

5. 测试契约,而非业务逻辑。 消费者测试验证响应结构和状态码。提供方验证确保契约能够满足。业务规则应由单元测试和集成测试负责。

Pact-JS 设置

在消费方和提供方都将 @pact-foundation/pact 安装为开发依赖。工作流分为两部分:

  • 消费方测试: 消费方声明它需要提供方满足什么条件(请求结构 + 预期响应)。运行测试会生成 pacts/<consumer>-<provider>.json ——即契约文件。使用 Matchers(Matchers.like、Matchers.eachLike、Matchers.integer、Matchers.string、Matchers.regex),让契约断言类型和格式,而非脆弱的精确值。
  • 提供方验证: 提供方使用 stateHandlers 设置每个 given(...) 状态所需的数据,并针对其真实实现(而非模拟对象)运行消费方的 pact,然后将验证结果发布回 Broker。

截至 2026 年 6 月的当前版本 Pact-JS v16 将 PactV4 重命名为 Pact,并将 MatchersV3 重命名为 Matchers。 旧名称已在 v16 中移除。如果你引用旧的博客文章或示例,请更新导入内容。API 行为不变。

对于事件驱动系统,同一个 Pact 类也支持消息 pact(Kafka、SNS/SQS、RabbitMQ)——消费方断言其预期消息的结构,提供方则验证其生产者发出的消息。

参见 references/pact-js-setup.md,了解安装命令、消费方测试(单个用户、404、分页列表)、带状态处理器和待处理 pact 的 Broker 驱动型提供方验证规范、Pact Broker Docker Compose,以及消息契约相关说明。

Pact Broker

Pact Broker 是用于发布 pact 文件和记录提供方验证结果的中央注册表。它支持 can-i-deploy 工作流。可通过基于 Postgres 的 Docker Compose 在本地运行;消费方 CI 会将带有提交 SHA 和分支标记的 pact 发布到其中。

从环境变量注入所有凭据 ——包括 Postgres 密码、Broker 数据库 URL 和基本身份验证密码。将其中任何内容硬编码到 compose 文件中,都会导致机密泄漏到版本控制中。将 Broker 镜像固定到已发布的标签,而不是 :latest。

参见 references/pact-js-setup.md,了解 docker-compose.pact-broker.yml 和 pact-broker publish 命令。

消费方驱动的工作流

完整周期:

1. Consumer writes contract test
   └── Generates pact JSON file

2. Consumer CI publishes pact to broker
   └── Broker stores pact tagged with consumer version + branch

3. Broker webhook triggers provider verification
   └── Provider CI pulls latest pact, runs verification

4. Provider publishes verification result to broker
   └── Broker records: "provider v2.3.1 satisfies consumer v1.5.0"

5. Before deploy: can-i-deploy check
   └── "Can consumer v1.5.0 be deployed? Yes, provider v2.3.1 is in production and verified."

两条流水线都会运行契约测试、将结果发布到 Broker,并通过 can-i-deploy 阻止部署。提供方流水线还会监听 repository_dispatch 事件,以便新 pact 自动触发验证。

参见 references/ci-pipelines.md,了解消费方 CI 工作流、提供方 CI 工作流(带 Postgres 服务 + 迁移),以及独立的 can-i-deploy / record-deployment 命令。

Pact Broker Webhook

在 Pact Broker 中配置 Webhook,以便在发布新 pact 时通过 repository_dispatch 触发提供方验证。Webhook 会向 https://api.github.com/repos/myorg/user-service/dispatches 发送 POST 请求,事件类型为 pact-changed;提供方流水线会监听此事件(参见 references/ci-pipelines.md 中的 repository_dispatch 触发器)。

待处理的 Pact(渐进式采用)

在提供方 Verifier 上设置 enablePending: true(以及 includeWipPactsSince),这样全新的消费方交互就能加入,而不会导致提供方构建失败——系统会报告该交互,但在消费方将其标记为预期之前不会让构建失败。这是在逐步添加契约时的标准安全保障。

以模式为先与以消费方为先

以消费方为先(Pact)

消费方定义自身需求;契约从实际使用模式中逐步形成。

适用场景: 消费方需求因客户端而异的团队(移动端需要的字段比 Web 端少)、自然演进的 API、微服务生态系统。

契约优先(OpenAPI + 验证)

提供方发布 OpenAPI 规范;使用方根据该规范验证自己的用法。

最适合: 拥有众多使用方的公共 API、在实现前预先设计的 API,以及 API 设计治理完善的团队。

OpenAPI 3.0 并非纯粹的 JSON Schema(例如 nullable: true)——vanilla Ajv 默认使用 draft 2020-12,会错误验证实际的 3.0 规范。请通过 ajv-formats 将 Ajv 配置为使用 OpenAPI 方言,或使用支持 OpenAPI 的验证器。有关 Ajv 配置以及针对规范验证 OpenAPI 的辅助工具,请参阅 references/schema-first.md 中的注意事项。

混合方法

使用 OpenAPI 作为设计文档,使用 Pact 作为强制执行机制。

  1. 使用 OpenAPI 规范设计 API(由提供方团队主导设计)。
  2. 根据 OpenAPI 规范生成 Pact 使用方测试,作为基线。
  3. 使用方在基线之外添加特定交互。
  4. 提供方根据 Pact 契约进行验证(OpenAPI 规范的子集)。
双向契约(PactFlow / SmartBear)

PactFlow(由 SmartBear 提供)支持双向契约测试,可将使用方 pact 与提供方验证解耦——提供方提供 OpenAPI 规范,使用方提供 pact,PactFlow 会检查两者是否兼容,无需提供方运行 pact 验证器。这是 PactFlow/SmartBear 的付费功能,不属于 Pact OSS。适用于以下情况:

  • 提供方团队无法或不愿在其 CI 中运行 Pact 验证器。
  • 提供方已将 OpenAPI 规范作为事实来源发布。
  • 你希望获得契约覆盖率,同时避免使用方仓库与提供方仓库紧密耦合。

权衡:双向检查比完整的 pact 验证更粗略——它们验证的是规范与契约的重叠部分,而非精确的运行时行为。可将其作为入门方式;待双方团队认可后,再升级为完整验证。

Schemathesis(基于属性、规范驱动)

对于 OpenAPI 优先的项目,Schemathesis(v4.x) 可直接根据规范对运行中的 API 执行基于属性的测试——生成数千条有效和无效请求,并检查响应是否符合规范。它能发现与 Pact 不同类型的错误(编码、边缘情况负载、状态码偏差)。建议搭配使用:Pact 用于由使用方驱动的交互,Schemathesis 用于由规范驱动的覆盖率。在 CI 中,优先使用 schemathesis/action@v3 Action,而不是直接执行 shell 命令。

避免使用:schemathesis run --base-url ... --hypothesis-deadline=2000(Schemathesis ≤ v3,自 v4.0 起已废弃,2025-06)。 v4 移除了 --hypothesis-deadline,并将 --base-url 更名为 --url;现在 schema 是位置参数。当前写法:schemathesis run ./openapi.yaml --url <base> --checks all。请参阅 references/schema-first.md。

can-i-deploy

can-i-deploy 命令是部署门禁。它会检查 Pact Broker 矩阵,以回答“根据 Broker 所知的一切,这个确切版本是否与目标环境中已有的内容兼容?”部署成功后,使用 record-deployment 记录部署,以确保矩阵保持准确。

始终传入 --retry-while-unknown <n> --retry-interval <s>。这能解决现实中最常见的问题:使用方刚发布 pact,而提供方尚未完成验证;如果不重试,门禁就会因竞态条件而直接失败,而不是等待验证结果。

绝不要在 can-i-deploy 检查未通过时部署,也绝不要在 main 上跳过该检查。 main 中的内容会进入生产环境——在此跳过门禁,就会发布一个尚未经过 Broker 确认兼容的版本,而这正是契约测试要防止的问题。

有关带注释的输出和重试标志,请参阅 references/ci-pipelines.md 中的 can-i-deploy 和 record-deployment 命令。

反模式

在契约中测试业务逻辑。 契约应保持精简:状态码、字段是否存在、字段类型、字段格式。业务逻辑应放在单元测试和集成测试中。

没有消费者参与,由提供方主导契约。 如果只有提供方团队定义契约,他们测试的只是自己认为消费者需要的内容,而非消费者实际使用的内容。消费者驱动的契约能捕捉真实的集成故障。

跳过提供方状态。 如果消费者期望 given("user 123 exists"),但提供方验证针对的是空数据库,那么验证毫无意义。提供方状态处理程序必须设置完全匹配的场景。

在生产 CI 中使用本地 pact 文件进行验证。 使用本地 pactUrls 验证时,只能看到磁盘上的 pact,看不到已部署的 pact。使用 pactBrokerUrl + consumerVersionSelectors 从 Broker 拉取 pact,确保验证反映消费者当前使用的版本。

从本地机器发布 pact。 pact 必须从 CI 发布,并带有已知的提交 SHA 和分支信息。本地发布会生成无法追溯的版本,污染 Broker。

忽略 can-i-deploy 失败。 如果 can-i-deploy 返回否,就修复契约违规问题,或与消费者团队协商变更。仍然部署会导致生产环境故障。

用一个庞大的 pact 覆盖每个端点。 从关键集成点开始。随着故障证明其必要性,再逐步添加契约(使用待处理 pact)。包含 500 个交互的 pact 无法维护。

不清理旧 pact。 配置 Pact Broker,删除超过 90 天且未部署到任何环境的 pact 版本。过时的 pact 会拖慢验证,让矩阵变得混乱。

验证

证明这些产物有效,先做最小检查:

  1. 消费者测试生成 pact。 运行 npm run test:contract,确认生成了 pacts/<consumer>-<provider>.json,且其中包含声明的交互。没有文件 = 没有契约。
  2. 针对真实服务的提供方验证通过。 启动测试数据库后运行 npm run test:contract:provider;每个消费者交互都应针对正在运行的提供方验证通过,而非针对模拟对象。
  3. Broker 往返验证。 使用 pact-broker publish ./pacts --consumer-app-version=$GIT_COMMIT --branch=$GIT_BRANCH 发布,并确认 pact 出现在 Broker 界面中,且已记录验证结果。
  4. 部署门禁。 运行 pact-broker can-i-deploy --pacticipant=<name> --version=<sha> --to-environment=production --dry-run,确认已知良好版本返回明确的“是/否”(而非“未知”)。

完成标准

  • 消费者 pact 测试在 CI 中运行;每次运行都会生成 pacts/*.json 文件并发布到 Broker,文件带有提交 SHA 和分支标记。
  • 每次提供方发生变更,以及发布新的 pact 时(通过 Pact Broker repository_dispatch webhook),CI 都会运行提供方验证任务,并从 Broker 拉取 pact,而非使用本地文件。
  • 消费者和提供方的 main 流水线均使用 can-i-deploy(带 --retry-while-unknown)作为部署门禁;契约遭到破坏时,任务会失败。
  • 存在 CONTRACTS.md(或 CODEOWNERS 条目),列明每项消费者与提供方交互的负责人/审查人。
  • 至少端到端运行并确认捕捉到一种破坏性变更场景:该场景在到达生产环境之前被 can-i-deploy 检查拦截。

参考文件(位于 references/ 中)

  • pact-js-setup.md — 安装命令、消费者 pact 测试(用户/404/分页)、使用状态处理程序和待处理 pact 的 Broker 驱动型提供方验证、消息契约说明,以及 Pact Broker Docker Compose 配置和发布命令。
  • ci-pipelines.md — 消费者和提供方 GitHub Actions 工作流,以及独立的 can-i-deploy(带重试标志)/record-deployment 命令。
  • schema-first.md — 使用 Ajv 对 OpenAPI 响应进行验证的辅助函数(含 3.0 方言注意事项),以及 Schemathesis v4 命令和 Action。

相关技能

  • api-testing — 验证你自己的 REST/GraphQL 端点(结构、状态、身份验证、标头)。一般的端点测试请使用这项技能;当其他团队需要使用你的 API,且你需要确保兼容性,而不只是共享一个架构时,请使用本技能。
  • service-virtualization — 通过存根或模拟依赖项来隔离测试。要用伪服务替换某个服务,请使用这项技能;要验证两个真实服务的接口一致,请使用本技能——契约用于验证兼容性,虚拟化则用于模拟。
  • ci-cd-integration — 流水线机制:将这些契约任务作为 CI 门禁运行、管理密钥以及并行化。若要了解本技能部署门禁所接入的流水线配置,请使用这项技能。
  • test-environments — 环境策略。若要确定契约验证的运行位置,以及如何在预发布和生产环境中配置代理,请使用这项技能。

评论

支持 Markdown