1.微服务集成测试的困境
在微服务架构中,服务之间通过HTTP或消息通信。传统的集成测试需要同时启动多个服务,环境搭建困难、执行缓慢。而且,若提供者的API发生变化(如删除字段、修改类型),消费者可能悄然失败。契约测试(ContractTesting)是一种解决方案:消费者定义期望的请求和响应,提供者验证这些期望是否满足。Pact是最流行的契约测试框架,有Java实现(PactJVM)。
参考:https://www.vrhyh.cn/category/yinshi.html
2.Pact的工作流程
Pact采用“消费者驱动”模式:
消费者测试:消费者编写测试,使用Pact的Mock服务,指定发送请求(如GET/user/123)应返回的响应体。Pact记录生成契约文件(JSON)。
契约发布:将契约文件发布到PactBroker(一个存储契约的中心服务)。
提供者测试:提供者从Broker拉取契约,针对自己的真实API运行验证,确保所有消费者的期望都满足。若不满足,测试失败。
CI/CD集成:消费者变更需重新验证提供者;提供者变更需验证所有消费者的契约,避免破坏下游。
3.Java中的Pact实现
使用au.com.dius:pact-jvm-consumer-junit5和pact-jvm-provider-junit5。消费者测试中,用@Pact注解定义交互,@PactVerification标记测试方法。Pact自动启动Mock服务器。提供者测试中,配置测试类加载契约,指定提供者名称,框架自动向真实服务发送请求并比对。
4.高级用法:消息契约
除了HTTP,Pact也支持异步消息(如Kafka)。消费者定义期望收到的消息体,提供者验证消息发布逻辑。这对于事件驱动架构非常有用。
参考:https://www.vrhyh.cn/category/zhongyi.html
5.案例:电商订单与库存服务的契约测试
订单服务(消费者)调用库存服务(提供者)的/stock接口锁定库存。双方约定:
请求:POST/stock/lock,body{"sku":"ABC","quantity":2}。
响应:200OK,body{"locked":true,"remaining":98}。
订单服务的单元测试使用Pact生成契约。库存服务在CI中运行Pact验证,确保接口符合契约。当库存服务准备增加字段(如添加batchNo)时,契约验证仍然通过(添加字段是兼容的);但如果删除remaining字段,验证失败,开发者意识到会破坏订单服务,从而谨慎修改。
团队使用PactBroker的WebUI查看所有消费者与提供者的依赖关系,版本管理清晰。
6.与SpringCloudContract的对比
SpringCloudContract是另一解决方案,但它是提供者驱动,需要提供者编写Groovy契约。Pact的消费者驱动更贴近现实:消费者提出需求,提供者实现并验证。Pact跨语言支持更好(消费者可以是Ruby,提供者是Java)。
7.实战注意事项
契约测试不能完全替代集成测试,但能快速反馈接口兼容性问题。
对每个消费者单独验证,避免“一个消费者改契约,其他消费者未感知”。
在CI中自动发布契约,提供者验证失败应阻断流水线。
使用pact-verifier的--pact-urls从Broker拉取,避免硬编码。
8.总结
Pact为Java微服务提供了高效的集成测试替代方案,尤其适合多个团队并行开发、频繁变更API的场景。通过消费者驱动的契约,团队可以独立演进,同时在接口层面保持可靠。
参考:https://www.vrhyh.cn