1. 契约测试基础
1.1 什么是契约测试
契约测试(Contract Testing)是一种介于单元测试与端到端测试之间的集成验证方法。它不直接部署真实的服务实例,而是通过预先定义的“契约”——即服务间交互的接口规范——来验证通信双方是否遵循相同的协议。契约测试的核心思想是:只要消费者和提供者都遵守同一份契约,集成就能正常工作。
核心价值:契约测试能在不启动完整环境的前提下,提前发现接口不兼容问题,将集成故障左移到开发阶段。
1.2 消费者驱动契约(CDC)
消费者驱动契约(Consumer-Driven Contract, CDC)是契约测试的主流模式。由服务的消费者定义其对提供者的期望,生成契约文件;提供者则根据该契约进行验证,确保自身实现满足所有消费者的需求。
| 角色 | 职责 | 典型动作 |
|---|---|---|
| 消费者 | 定义期望的请求/响应格式 | 编写测试用例,生成契约文件(如 Pact 格式) |
| 提供者 | 验证自身实现是否满足契约 | 运行契约验证套件,报告失败点 |
| 契约仓库 | 存储和分发契约文件 | Pact Broker、本地文件系统或 Git 仓库 |
CDC 工作流:消费者编写测试 → 生成契约 → 发布到契约仓库 → 提供者拉取契约 → 运行验证 → 反馈结果。
1.3 提供者验证
提供者验证是契约测试的另一半。提供者从契约仓库获取消费者定义的契约文件,然后运行验证测试,检查自己的 API 实现是否与契约中的每个交互匹配。验证内容包括:
- 请求路径与方法:是否接受正确的 HTTP 方法(GET/POST/PUT/DELETE)和 URL 路径。
- 请求头与参数:是否处理消费者发送的 Header、Query 参数、Body 字段。
- 响应状态码:是否返回契约中约定的 HTTP 状态码(如 200、201、404)。
- 响应体结构:返回的 JSON/XML 字段名、类型、嵌套层级是否与契约一致。
- 错误场景:是否在异常情况下返回约定的错误格式(如 400 错误响应)。
常见陷阱:提供者验证只检查接口的“形状”(结构、类型、状态码),不验证业务逻辑的正确性。例如,契约要求返回
{"balance": 100},验证只确认字段存在且为数字,不检查余额计算是否正确。
1.4 契约测试 vs 集成测试
契约测试与集成测试经常被混淆,但两者在目标、范围、执行方式上有本质区别。下表从多个维度进行对比:
| 维度 | 契约测试 | 集成测试 |
|---|---|---|
| 目标 | 验证接口契约一致性 | 验证组件间协作正确性 |
| 环境 | 模拟/桩(Mock/Stub) | 真实服务实例(可部分模拟) |
| 执行速度 | 快(毫秒级) | 慢(秒级到分钟级) |
| 依赖管理 | 无外部依赖(契约文件即可) | 需要启动数据库、消息队列等 |
| 故障定位 | 精确到字段级别 | 需要日志和链路追踪 |
| 维护成本 | 低(契约自动生成) | 高(环境配置复杂) |
| 典型工具 | Pact、Spring Cloud Contract | Testcontainers、Docker Compose |
选择建议:契约测试适合验证 API 兼容性,集成测试适合验证端到端业务流程。两者互补,不应相互替代。推荐在 CI 流水线中先运行契约测试(快速反馈),再运行集成测试(深度验证)。
1.5 契约测试在 Go 项目中的定位
在 Go 微服务架构中,契约测试通常作为 CI 流水线的第一道集成防线。推荐在以下场景优先引入:
- 服务间 HTTP/gRPC 调用:消费者与提供者之间通过 RESTful API 或 Protobuf 通信。
- 第三方 API 集成:对外部服务(如支付网关、消息推送)的调用进行契约保护。
- 内部库/模块接口:同一仓库内不同 package 之间的接口约定。