背景
日常开发过程中,项目的接口通常由服务提供方约定和提供,微服务模式下接口被多个消费者调用更是常态,那么提供方接口的变更如何快速、高效、无遗漏的通知给消费者呢?另外,当一个service同时被多个使用者调用,如何保证对service的修改可以让其它所有使用者造成的影响都能被感知到?这些问题契约测试可以给你答案。
测试问题
假设我们有一个由多个微服务组成的应用程序,如下图所示:
如果我们想测试服务间通信时,通常有两种方式:
- 部署所有微服务并执行端到端测试
- 优点:
- 模拟生产(真实集成场景)
- 测试服务之间的真实通信
- 缺点:
- 要测试一个微服务,我们必须部署其他相关的微服务,数据库及其他基础设施
- 运行时间过长,当其他服务不稳定或者请求时间过长时,就会导致测试效率很低,稳定性下降
- 反馈不及时
- 难以调试
- 优点:
- 在单元/集成测试中MOCK其他微服务(构建测试替身)
- 优点:
- 快速反馈
- 没有基础设施要求
- 缺点:
- 无法确保接口变动的安全性和准确性
- 测试可以通过但到生产时会失败(当内部系统测试都通过时,如何能保证真正的外部API没有变化?)
- 优点:
Spring Cloud Contract契约测试的出现就是为了解决上述问题。主要思想是为您提供非常快速的反馈而无需建立整个微服务链路,使用存根(替身),您唯一需要的就是您直接使用的应用程序,下图显示了存根与应用程序的关系:
契约测试分两种类型,一种是消费者驱动,一种是提供者驱动,其中最常用的,是消费者驱动的契约测试(Consumer-Driven Contract Test,简称 CDC)。核心思想是从消费者业务实现的角度出发,由消费者端定义需要的数据格式以及交互细节,生成一份契约文件,然后生产者根据契约文件来实现自己的逻辑,并在持续集成环境中持续验证该实现结果是否正确。
对于基于Restful API的微服务来说,它的契约就是指 API 的请求和响应的规则:
对于请求,包括请求URL、请求头、请求内容等
对于响应,包括状态码、响应头、响应内容等
1 | request: |
Contract DSL Dynamic properties
Contract DSL Dynamic properties in Body
This section is valid only for the Coded DSL (Groovy, Java etc.)
1 | org.springframework.cloud.contract.spec.Contract.make { |
Contract DSL Regex
This section is valid only for Groovy DSL.
Contract DSL Matchers
SCC契约测试集成
契约测试能给我们带来什么?
降低服务集成的难度:把服务集成这个过程分解成了更细的单元测试和接口测试,它从消费者的需求为出发点,把消费者的需求作为测试用例驱动实现一份契约,然后验证提供者端的功能;
开发并行,提高开发效率:契约隔离了消费者和提供者,双方可以并行开展工作,开发过程中就利用契约进行预集成测试,不用等到联调再来集成调通接口,一旦成熟,在保证质量的前提下,联调的成本可以减低到几乎为0;
服务接口变更快速感知,确保变动的安全性和准确性:只要有变化,契约测试即可第一时间发现,保证安全和对接的准确性。
总结
在微服务模式下,服务间的调用关系复杂,契约测试是保证服务提高质量的重要手段之一,因此建议充分利用。