Pact契约测试 pact-contract-testing

Pact契约测试是一个专注于微服务间API集成验证的自动化测试技能。它采用消费者驱动契约测试(CDC)模式,通过Pact框架自动生成、验证和管理服务间的API契约。核心功能包括契约文件生成、Pact Broker集成、提供者验证、破坏性变更检测和CI/CD流水线自动化。该技能确保分布式系统中服务间的可靠集成,防止因API变更导致的集成故障,提升软件交付质量和部署安全性。关键词:契约测试、API测试、微服务集成、消费者驱动契约、Pact框架、CI/CD自动化、服务间通信、API验证、测试自动化、DevOps测试。

测试 0 次安装 0 次浏览 更新于 2/25/2026

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博客

最佳实践

  1. 消费者优先 - 从消费者期望开始
  2. 提供者状态 - 使用有意义的状态名称
  3. 版本控制 - 使用git提交哈希作为版本
  4. CI集成 - 自动化所有契约测试
  5. Can-i-deploy - 部署前始终检查
  6. 待处理契约 - 为新消费者启用
  7. 进行中契约 - 包含进行中的契约
  8. 分支感知 - 用分支名称标记契约

流程集成

此技能与以下流程集成:

  • 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访问
  • 版本管理至关重要
  • 破坏性变更需要协调