name: pact-contract-testing description: 使用Pact框架进行消费者驱动的契约测试。生成消费者契约,配置Pact Broker发布,执行提供者验证,检测破坏性变更,并与CI/CD流水线集成。 allowed-tools: Bash(*) Read Write Edit Glob Grep WebFetch metadata: author: babysitter-sdk version: “1.0.0” category: contract-testing backlog-id: SK-012
pact-contract-testing
您是 pact-contract-testing - 一个专门用于使用Pact框架进行消费者驱动的契约测试的技能,可在服务之间实现可靠的API集成测试。
概述
此技能支持AI驱动的契约测试,包括:
- 生成消费者契约(Pact文件)
- 配置Pact Broker发布
- 提供者验证执行
- 破坏性变更检测
- 用于CI/CD的Webhook集成
- Can-i-deploy检查
- 契约版本管理
- 双向契约测试
先决条件
- Node.js、Java或Python环境
- 适用于您语言的Pact库
- Pact Broker(自托管或PactFlow)
- CI/CD流水线访问权限
- 消费者和提供者应用程序
能力
1. 消费者契约生成
使用Pact JS创建消费者端契约:
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
const { like, eachLike, regex } = MatchersV3;
const provider = new PactV3({
consumer: 'frontend-app',
provider: 'user-service',
logLevel: 'info'
});
describe('用户API契约', () => {
it('应通过ID返回用户', async () => {
// 安排:定义预期的交互
await provider
.given('存在ID为123的用户')
.uponReceiving('对用户123的请求')
.withRequest({
method: 'GET',
path: '/api/users/123',
headers: {
Accept: 'application/json',
Authorization: regex(/Bearer .+/, 'Bearer token123')
}
})
.willRespondWith({
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
id: like(123),
email: like('user@example.com'),
name: like('John Doe'),
createdAt: like('2024-01-15T10:30:00Z'),
roles: eachLike('user')
}
});
// 执行与断言:执行测试
await provider.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/users/123`, {
headers: {
Accept: 'application/json',
Authorization: 'Bearer token123'
}
});
expect(response.status).toBe(200);
const user = await response.json();
expect(user.id).toBe(123);
});
});
it('对于不存在的用户应返回404', async () => {
await provider
.given('用户999不存在')
.uponReceiving('对不存在的用户的请求')
.withRequest({
method: 'GET',
path: '/api/users/999'
})
.willRespondWith({
status: 404,
body: {
error: like('用户未找到'),
code: like('USER_NOT_FOUND')
}
});
await provider.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/users/999`);
expect(response.status).toBe(404);
});
});
});
2. 提供者验证
根据契约验证提供者:
import { Verifier } from '@pact-foundation/pact';
const verifier = new Verifier({
provider: 'user-service',
providerBaseUrl: 'http://localhost:3000',
// 从broker获取契约
pactBrokerUrl: 'https://your-broker.pactflow.io',
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
// 提供者版本
providerVersion: process.env.GIT_COMMIT || '1.0.0',
providerVersionBranch: process.env.GIT_BRANCH || 'main',
// 状态处理器
stateHandlers: {
'存在ID为123的用户': async () => {
// 设置测试数据
await db.users.create({ id: 123, email: 'user@example.com', name: 'John Doe' });
},
'用户999不存在': async () => {
// 确保用户不存在
await db.users.delete(999);
}
},
// 发布结果
publishVerificationResult: true,
enablePending: true,
includeWipPactsSince: '2024-01-01'
});
describe('提供者验证', () => {
beforeAll(async () => {
// 启动提供者服务
await startServer();
});
afterAll(async () => {
await stopServer();
});
it('应验证所有消费者契约', async () => {
await verifier.verifyProvider();
});
});
3. Pact Broker发布
将契约发布到Pact Broker:
import { Publisher } from '@pact-foundation/pact';
const publisher = new Publisher({
pactFilesOrDirs: ['./pacts'],
pactBroker: 'https://your-broker.pactflow.io',
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
consumerVersion: process.env.GIT_COMMIT || '1.0.0',
branch: process.env.GIT_BRANCH || 'main',
tags: [process.env.GIT_BRANCH || 'main']
});
await publisher.publishPacts();
4. Can-I-Deploy检查
验证部署安全性:
# 检查消费者是否可以部署
pact-broker can-i-deploy \
--pacticipant frontend-app \
--version $(git rev-parse HEAD) \
--to-environment production \
--broker-base-url https://your-broker.pactflow.io \
--broker-token $PACT_BROKER_TOKEN
# 检查提供者是否可以部署
pact-broker can-i-deploy \
--pacticipant user-service \
--version $(git rev-parse HEAD) \
--to-environment production \
--broker-base-url https://your-broker.pactflow.io \
--broker-token $PACT_BROKER_TOKEN
# 记录部署
pact-broker record-deployment \
--pacticipant user-service \
--version $(git rev-parse HEAD) \
--environment production \
--broker-base-url https://your-broker.pactflow.io \
--broker-token $PACT_BROKER_TOKEN
5. CI/CD集成
GitHub Actions工作流:
name: 契约测试
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
PACT_BROKER_URL: https://your-broker.pactflow.io
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
jobs:
consumer-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 安装依赖
run: npm ci
- name: 运行消费者契约测试
run: npm run test:contract:consumer
- name: 发布契约
run: |
npx pact-broker publish ./pacts \
--consumer-app-version ${{ github.sha }} \
--branch ${{ github.ref_name }} \
--broker-base-url $PACT_BROKER_URL \
--broker-token $PACT_BROKER_TOKEN
provider-verification:
runs-on: ubuntu-latest
needs: consumer-tests
steps:
- uses: actions/checkout@v4
- name: 设置Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 安装依赖
run: npm ci
- name: 启动提供者
run: npm run start:test &
- name: 验证提供者
run: npm run test:contract:provider
can-i-deploy:
runs-on: ubuntu-latest
needs: [consumer-tests, provider-verification]
if: github.ref == 'refs/heads/main'
steps:
- name: 我可以部署吗?
run: |
docker run --rm pactfoundation/pact-cli \
broker can-i-deploy \
--pacticipant frontend-app \
--version ${{ github.sha }} \
--to-environment production \
--broker-base-url $PACT_BROKER_URL \
--broker-token $PACT_BROKER_TOKEN
6. Webhook配置
设置Pact Broker webhooks:
# 在消费者变更时触发提供者验证
pact-broker create-webhook \
'https://api.github.com/repos/org/provider-repo/dispatches' \
--request=POST \
--header 'Accept: application/vnd.github.v3+json' \
--header 'Authorization: Bearer ${GITHUB_TOKEN}' \
--data '{"event_type": "contract_requiring_verification", "client_payload": {"pact_url": "${pactbroker.pactUrl}"}}' \
--description "在契约变更时触发提供者验证" \
--contract-content-changed \
--broker-base-url https://your-broker.pactflow.io \
--broker-token $PACT_BROKER_TOKEN
7. 双向契约测试
与OpenAPI规范一起使用:
// 提供者发布OpenAPI规范
import { PactV3 } from '@pact-foundation/pact';
// 消费者针对提供者发布的OpenAPI进行测试
const provider = new PactV3({
consumer: 'frontend-app',
provider: 'user-service',
pactBrokerUrl: 'https://your-broker.pactflow.io',
pactBrokerToken: process.env.PACT_BROKER_TOKEN
});
// 提供者发布OAS
// pact-broker publish-provider-contract \
// openapi.yaml \
// --provider user-service \
// --provider-app-version $(git rev-parse HEAD) \
// --branch main \
// --content-type application/yaml \
// --verification-success \
// --broker-base-url https://your-broker.pactflow.io \
// --broker-token $PACT_BROKER_TOKEN
8. 匹配器和生成器
使用灵活的匹配:
import { MatchersV3 } from '@pact-foundation/pact';
const {
like, // 类型匹配
eachLike, // 数组匹配
regex, // 正则表达式匹配
integer, // 整数类型
decimal, // 小数类型
boolean, // 布尔类型
string, // 字符串类型
datetime, // ISO日期时间
uuid, // UUID格式
ipv4Address, // IPv4地址
email, // 电子邮件格式
atLeastOneLike, // 至少一个项目匹配
atMostLike, // 最多N个项目匹配
constrainedArrayLike // 最小/最大数组
} = MatchersV3;
const userContract = {
id: uuid(),
email: email('test@example.com'),
name: string('John Doe'),
age: integer(25),
balance: decimal(100.50),
isActive: boolean(true),
createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
roles: eachLike('user'),
preferences: like({
theme: 'dark',
notifications: true
}),
tags: constrainedArrayLike('tag', 1, 5)
};
MCP服务器集成
此技能可以利用以下MCP服务器增强能力:
| 服务器 | 描述 | 安装 |
|---|---|---|
| PactFlow MCP服务器 | IDE中的AI驱动契约测试 | PactFlow博客 |
最佳实践
- 消费者优先 - 从消费者期望开始
- 提供者状态 - 使用有意义的状态名称
- 版本控制 - 使用git提交哈希作为版本
- CI集成 - 自动化所有契约测试
- Can-i-deploy - 部署前始终检查
- 待处理契约 - 为新消费者启用
- 进行中契约 - 包含进行中的契约
- 分支感知 - 用分支名称标记契约
流程集成
此技能与以下流程集成:
contract-testing.js- 契约测试的所有阶段api-testing.js- API契约验证continuous-testing.js- CI/CD契约集成quality-gates.js- 契约验证门
输出格式
执行操作时,提供结构化输出:
{
"operation": "verify",
"provider": "user-service",
"providerVersion": "abc123",
"consumers": [
{
"name": "frontend-app",
"version": "def456",
"status": "passed",
"interactions": 5,
"passed": 5,
"failed": 0
}
],
"canDeploy": true,
"environment": "production",
"verificationUrl": "https://broker.pactflow.io/verifications/123"
}
错误处理
- 优雅地处理缺少的提供者状态
- 提供清晰的失配描述
- 失败时记录完整的请求/响应
- 支持重试以处理暂时性故障
- 清晰地记录破坏性变更
约束
- 契约代表消费者需求,而非完整API
- 提供者状态必须可重现
- Broker必须可从CI/CD访问
- 版本管理至关重要
- 破坏性变更需要协调