4. 契约测试基础

4.1 消费者驱动契约(CDC)核心概念

在微服务架构中,服务之间的交互依赖接口定义与数据格式。传统集成测试需要部署所有依赖服务,成本高且脆弱。消费者驱动契约(Consumer-Driven Contracts, CDC)提供了一种轻量级、可并行化的验证方式:由消费者(Consumer)定义其对提供者(Provider)接口的期望,形成一份“契约”,提供者据此验证自身实现是否满足消费者需求。

CDC 的核心思想是“信任但验证”——消费者明确声明需要什么,提供者保证提供什么,双方通过契约解耦。

CDC 关键角色:
  • 消费者(Consumer):调用外部服务的一方,负责编写契约(期望的请求与响应)。
  • 提供者(Provider):被调用的一方,负责验证契约是否被满足。
  • 契约(Contract):一份结构化文档,记录消费者对提供者接口的期望(如 HTTP 方法、路径、请求体、响应状态码、响应体字段)。
  • 契约仓库(Pact Broker):存储、版本化、共享契约的中央仓库,支持消费者与提供者之间的协作。

CDC 的工作流程通常包含以下步骤:

  1. 消费者编写测试:消费者使用 Pact 框架编写单元测试,定义对提供者接口的期望(如“发送 POST /orders 请求,期望返回 201 状态码及订单 ID”)。
  2. 生成契约文件:消费者测试运行时,Pact 框架自动生成一份 JSON 格式的契约文件(pact 文件)。
  3. 发布契约:消费者将契约文件上传至 Pact Broker。
  4. 提供者验证:提供者从 Pact Broker 下载最新契约,运行验证测试,检查自身 API 是否满足所有消费者期望。
  5. 反馈结果:验证通过则发布成功状态;失败则通知消费者或阻断发布流水线。
消费者驱动契约(CDC)工作流程 消费者驱动契约(CDC)工作流程 消费者 编写测试 & 生成契约 提供者 验证契约 Pact Broker 存储 & 共享契约 发布契约 拉取契约 验证结果反馈 💡 契约是消费者与提供者之间的“协议”,由消费者驱动定义 提供者只需验证契约,无需了解消费者内部实现

4.2 Pact 框架简介

Pact 是目前最流行的 CDC 实现框架,支持多种编程语言(Java、JavaScript、Python、Go、.NET 等)。它提供了一套完整的工具链,帮助团队在微服务集成中实现契约测试。

特性 说明
消费者端(Pact JVM / Pact JS) 提供 DSL 编写消费者测试,自动生成契约文件(JSON 格式)。
提供者端(Pact Provider Verification) 提供验证框架,自动匹配消费者期望与实际 API 响应。
Pact Broker 中央契约仓库,支持版本管理、环境标签、Webhook 触发。
匹配规则(Matching Rules) 支持精确匹配、类型匹配、正则匹配,避免因无关字段变化导致测试失败。
Provider States 允许提供者针对不同数据状态(如“订单已存在”“订单不存在”)准备测试数据。
Pact 核心优势:
  • 轻量级:无需部署真实服务,消费者测试在本地 mock 提供者响应。
  • 可并行:消费者与提供者可以独立开发、独立测试。
  • 可追溯:契约文件版本化,支持历史回溯与变更影响分析。
  • CI/CD 友好:可集成到流水线中,自动验证契约兼容性。

以下是一个 Pact 消费者测试的典型代码示例(Java + JUnit 5):

// 消费者测试:定义对提供者 /api/users/1 的期望
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "UserService", port = "8080")
public class UserServiceConsumerTest {

    @Pact(consumer = "OrderService")
    public V4Pact createUserPact(PactDslWithProvider builder) {
        return builder
            .given("用户 ID 1 存在")  // Provider State
            .uponReceiving("获取用户信息")
                .path("/api/users/1")
                .method("GET")
            .willRespondWith()
                .status(200)
                .headers(Map.of("Content-Type", "application/json"))
                .body(new PactDslJsonBody()
                    .integerType("id", 1)
                    .stringType("name", "张三")
                    .stringType("email", "zhangsan@example.com"))
            .toPact();
    }

    @Test
    @PactTestFor(pactMethod = "createUserPact")
    public void testGetUser(MockServer mockServer) {
        // 使用 mockServer 的 URL 发起真实 HTTP 请求
        UserClient client = new UserClient(mockServer.getUrl());
        User user = client.getUser(1);
        assertEquals(1, user.getId());
        assertEquals("张三", user.getName());
    }
}

提供者验证代码示例:

// 提供者验证:验证实际 API 是否满足消费者契约
@Provider("UserService")
@PactBroker(url = "http://pact-broker:9292")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserServiceProviderTest {

    @LocalServerPort
    int port;

    @BeforeEach
    void setup(PactVerificationContext context) {
        context.setTarget(new HttpTestTarget("localhost", port));
    }

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void pactVerificationTestTemplate(PactVerificationContext context) {
        context.verifyInteraction();
    }

    @State("用户 ID 1 存在")
    public void userExists() {
        // 准备测试数据:确保数据库中存在 ID=1 的用户
        userRepository.save(new User(1, "张三", "zhangsan@example.com"));
    }
}

4.3 契约测试与单元测试、集成测试的区别

契约测试在测试金字塔中位于单元测试与集成测试之间,填补了“接口兼容性验证”的空白。三者对比如下:

维度 单元测试 契约测试 集成测试
测试范围 单个函数/方法/类 服务间接口交互 多个服务/组件协同
依赖管理 无外部依赖(mock/stub) 消费者端 mock 提供者;提供者端真实 API 需要部署所有依赖服务
运行速度 毫秒级 秒级(消费者端快,提供者端稍慢) 分钟级(依赖启动与网络通信)
验证目标 业务逻辑正确性 接口契约兼容性 端到端功能与数据流
失败原因 代码逻辑错误 接口变更不兼容 环境/配置/网络/数据问题
维护成本 低 中(需维护契约文件) 高(环境搭建与数据准备)
CI/CD 集成 每次提交运行 消费者/提供者各自流水线中运行 部署前或定期运行
常见误区:
  • 契约测试 ≠ 集成测试:契约测试只验证接口格式与协议,不验证端到端业务逻辑;集成测试需要真实服务协作。
  • 契约测试 ≠ 单元测试:单元测试关注内部逻辑,契约测试关注外部接口行为。
  • 不要用契约测试替代集成测试:两者互补,契约测试保证接口兼容,集成测试保证整体功能正确。
测试金字塔中的契约测试位置 测试金字塔中的契约测试位置 单元测试 数量多 · 速度快 · 无外部依赖 契约测试 接口兼容性验证 集成测试 端到端验证 契约测试定位 介于单元测试与 集成测试之间 专注接口兼容性 测试策略建议 单元测试覆盖逻辑 契约测试覆盖接口 集成测试覆盖流程

4.4 本章小结

  • 消费者驱动契约(CDC)是一种由消费者定义接口期望、提供者验证实现的测试方法,核心目标是保证服务间接口兼容性。
  • Pact 框架是 CDC 的主流实现,提供消费者测试、提供者验证、契约仓库(Pact Broker)等完整工具链。
  • 契约测试位于测试金字塔中层,与单元测试(关注内部逻辑)和集成测试(关注端到端功能)互补,不能相互替代。
  • 在微服务集成环境回放场景中,契约测试是“回放验证”的基础——只有接口契约一致,回放的数据流才有意义。