微服务为什么需要契约测试?
简化版
契约测试验证服务提供方的实际接口是否满足消费者依赖,能在 CI 阶段发现字段、状态码、Schema 和语义的不兼容变更。它比全链路测试更快、更易定位,但不能替代业务端到端验证和生产观测。
详细版
消费者驱动契约由消费者声明最小请求与响应期望,发布到 Broker 后由提供方对真实实现验证;Schema 驱动方式则从 OpenAPI、Protobuf 等检查双向兼容。契约必须版本化并关联消费者版本,失败时阻断发布或要求迁移。
测试覆盖成功、错误、可选字段、未知枚举和幂等语义,样例避免过度指定无关字段。消息契约还要验证 header、分区 key 和演进规则;只有仍在生产或保留窗口内的消费者契约才应阻断提供方。
完整版教学
一、它填补什么空白
单元测试只证明本服务逻辑,端到端测试又慢且故障定位困难。契约测试在两者之间验证接口边界,让提供方无需启动所有消费者也能知道一次变更会破坏谁。
心法:契约测试保护的是消费者真正依赖的行为,不是把整个响应快照冻结。
二、消费者驱动流程
consumer creates pact -> broker stores version
provider verifies pact -> publish verification
deployment checks matrix -> allow / block
消费者测试产生可执行期望,提供方 CI 对真实路由运行它;部署检查当前环境中消费者版本与提供方版本是否兼容。
三、什么算契约
| 类型 | 示例 | 验证重点 |
|---|---|---|
| HTTP | 路径、状态码、字段 | 可选性与错误语义 |
| RPC | 方法、字段编号 | 前后兼容 |
| 消息 | Schema、header、key | 异步演进与路由 |
| 行为 | 幂等、分页 | 多次调用语义 |
只验证 JSON 能解析仍不够;把 404 改成空对象会改变消费者分支,也属于破坏。
四、避免脆弱契约
消费者只声明使用的字段,不要固定时间戳、数组顺序或无关 header。若契约镜像整个响应,提供方任何安全新增字段都可能导致无意义失败。
另一方面,不能用任意值匹配掩盖单位和枚举。金额为正整数、货币属于已知集合等业务约束应明确。
五、版本和环境矩阵
提供方 v3 必须满足生产中消费者 A v5、B v2 的有效契约。已下线消费者应从矩阵撤销,否则僵尸契约会永久阻塞演进;撤销需要依据部署清单和保留窗口自动判断。
金丝雀环境验证通过不代表生产矩阵通过,部署门禁必须使用目标环境实际版本。
六、消息契约的特殊点
消息会在 Broker、归档和 DLQ 中存活很久。删除字段前要覆盖最长重放窗口;新增枚举要求旧消费者有 UNKNOWN 路径。测试还应验证事件 key,否则同聚合顺序可能被破坏。
契约测试不证明消息一定送达,可靠发布、幂等消费和对账属于运行时责任。
Provider State 应由测试夹具显式建立,例如“订单不存在”,不能依赖共享环境里碰巧存在的数据,否则契约测试会随机失败。
敏感生产样本应脱敏后再进入契约,避免测试仓库与 Broker 成为数据泄漏旁路。
七、常见误区与追问
- 误区:有 OpenAPI 就不需要契约测试。 文档不证明实现和消费者期望一致。
- 误区:快照越完整保护越强。 过度指定会制造脆弱测试。
- 误区:契约通过就无需端到端测试。 跨服务业务流程仍需少量验证。
- 误区:只测成功响应即可。 错误码和重试语义同样是契约。
- 追问:谁维护契约? 消费者声明期望,提供方共同评审并验证。
- 追问:僵尸契约如何清理? 关联部署版本、到期策略和生产流量证据。
- 追问:不兼容变更怎么办? 展开—收缩、多版本并存并迁移消费者。
八、加强记忆
契约测试沿“消费者声明最小期望—Broker 版本化—提供方验证真实实现—部署矩阵门禁”运行。它把接口破坏提前到 CI,同时用最小匹配避免脆弱;消息还要覆盖历史保留和 key 语义,但交付可靠性与业务流程仍由其他测试和观测补齐。