关于 contract-testing
面向采用 Pact 的团队,指导构建消费者驱动的契约测试流程,覆盖消费者测试、提供方验证和部署门禁。通过 Pact Broker 与 can-i-deploy 检查兼容性,帮助服务独立发布。
主要功能
- 01生成 Pact 消费者测试
- 02通过 Broker 验证提供方
- 03使用 can-i-deploy 阻止不兼容部署
- 04支持 HTTP 与消息契约
- 05提供 OpenAPI 与 Pact 混合方案
适用场景
- 01在 CI 中发现接口字段变更造成的兼容问题
- 02在没有共享集成环境时独立部署服务
- 03为 Kafka 或队列消息验证契约
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill contract-testing安装后在 Claude Code 中输入/contract-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ci-pipelines.mdpact-js-setup.mdschema-first.md详细介绍
发现问题
先检查 .agents/qa-project-context.md——如果文件存在,就使用其中的信息,并跳过已回答的问题。然后:
- 架构: 微服务、带有独立消费者(移动端/SPA)的单体,还是 BFF 模式?团队独立部署时,契约测试最为重要。
- 谁拥有契约? 消费者驱动(由消费者定义所需内容),还是提供方驱动(由提供方发布规范)?大多数团队采用消费者驱动的方式会受益。
- API 版本策略: 基于 URL(
/v1/、/v2/)、基于请求头,还是没有版本策略?契约必须考虑版本协商。 - 有多少组消费者—提供方配对? 从流量最高或最脆弱的集成开始。不要试图一次性为所有内容编写契约测试。
- 现有 API 规范: 是否有 OpenAPI/Swagger 规范?如果有,可以考虑从模式优先的契约(或 Schemathesis)入手。
- 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 作为强制执行机制。
- 使用 OpenAPI 规范设计 API(由提供方团队主导设计)。
- 根据 OpenAPI 规范生成 Pact 使用方测试,作为基线。
- 使用方在基线之外添加特定交互。
- 提供方根据 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 会拖慢验证,让矩阵变得混乱。
验证
证明这些产物有效,先做最小检查:
- 消费者测试生成 pact。 运行
npm run test:contract,确认生成了pacts/<consumer>-<provider>.json,且其中包含声明的交互。没有文件 = 没有契约。 - 针对真实服务的提供方验证通过。 启动测试数据库后运行
npm run test:contract:provider;每个消费者交互都应针对正在运行的提供方验证通过,而非针对模拟对象。 - Broker 往返验证。 使用
pact-broker publish ./pacts --consumer-app-version=$GIT_COMMIT --branch=$GIT_BRANCH发布,并确认 pact 出现在 Broker 界面中,且已记录验证结果。 - 部署门禁。 运行
pact-broker can-i-deploy --pacticipant=<name> --version=<sha> --to-environment=production --dry-run,确认已知良好版本返回明确的“是/否”(而非“未知”)。
完成标准
- 消费者 pact 测试在 CI 中运行;每次运行都会生成
pacts/*.json文件并发布到 Broker,文件带有提交 SHA 和分支标记。 - 每次提供方发生变更,以及发布新的 pact 时(通过 Pact Broker
repository_dispatchwebhook),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 — 环境策略。若要确定契约验证的运行位置,以及如何在预发布和生产环境中配置代理,请使用这项技能。