主要用于在微服务架构下做CDC(消费者驱动契约)测试。下图展示了多个微服务的调用,如果我们更改了一个模块要如何进行测试呢?
传统的两种测试思路
模拟生产环境部署所有的微服务,然后进行测试
优点
测试结果可信度高
缺点
测试成本太大,装一整套环境耗时,耗力,耗机器
Mock其他微服务做端到端的测试
优点
不用装整套产品了,测的也方便快捷
缺点
需要写很多服务的Mock,要维护一大堆不同版本用途的simulate(模拟器),同样耗时耗力
每个服务都生产可被验证的 Stub Runner,通过WireMock调用,服务双方签订契约,一方变化就更新自己的Stub,并且测对方的Stub。Stub其实只提供了数据,也就是契约,可以很轻量的模拟服务的请求返回。而Mock可在Stub的基础上增加验证
契约测试流程服务提供者
编写契约,可以用Groovy DSL 脚本也可以用 YAML文件
编写测试基类用于构建过程中插件自动生成测试用例
生成的测试用例会自动运行,这时如果我么提供的服务不能满足契约中的规则就会失败
提供者不断完善功能直到服务满足契约要求
发布Jar包,同时将Stub后缀的jar一同发布
服务消费者
对需要依赖外部服务的接口编写测试用例
通过注解指定需要依赖服务的Stub jar包
验证外部服务没有问题
简单案例 服务提供者模拟一个股票价格查询的服务
项目地址springcloud-contract-provider-rest
项目结构 项目依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-verifier</artifactId> <scope>test</scope> </dependency> <build> <plugins> <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>2.2.1.RELEASE</version> <extensions>true</extensions> <configuration> <!--用于构建过程中插件自动生成测试用例的基类--> <baseClassForTests> com.example.springcloudcontractproviderrest.RestBaseCase </baseClassForTests> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> 编写契约既然是消费者驱动契约,我么首先需要制定契约,这里为了方便假设查询贵州茅台的股价返回值是固定的999,也可以通过正则等方式去限制返回值
Contract.make { description "query by id should return stock(id,price)" request { method GET() url value { // 消费者使用时请求任何 /stock/price/数字 都会被转为 /stock/price/600519 consumer regex('/stock/price/\\d+') producer "/stock/price/600519" } } response { status OK() headers { contentType applicationJson() } // 提供给消费者的默认返回 body([ id : 600519, price: 999 ]) // 服务端在测试过程中,body需要满足的规则 bodyMatchers { jsonPath '$.id', byRegex(number()) jsonPath '$.price', byRegex(number()) } } } 测试基类主要是加载环境,然后由于不是真实环境模拟了数据库查询
@SpringBootTest @RunWith(SpringRunner.class) public class RestBaseCase { @Autowired private StockController stockController; @MockBean private StockRepository stockRepository; @Before public void setup() { init(); RestAssuredMockMvc.standaloneSetup(stockController); } private void init() { Mockito.when(stockRepository.getStockById(600519)).thenReturn(new StockDTO(600519, "贵州茅台", 999L, "SH")); } } 实现服务并测试实现我们的服务功能,具体代码逻辑可以在项目地址中查看,然后测试看是否符合契约
mvn clean test
可以在生成(target)目录中找到 generated-test-sources 这个目录,插件为我们自动生成并且运行的case就在其中
public class StockTest extends RestBaseCase { @Test public void validate_shoudReturnStockIdAndPrice() throws Exception { // given: MockMvcRequestSpecification request = given(); // when: ResponseOptions response = given().spec(request) .get("/stock/price/600519"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); // and: assertThat(parsedJson.read("$.id", String.class)).matches("-?(\\d*\\.\\d+|\\d+)"); assertThat(parsedJson.read("$.price", String.class)).matches("-?(\\d*\\.\\d+|\\d+)"); } } 发布如果一切顺利就可以deploy了
服务消费者模拟查询个人资产的服务,需要远程调用股票价格查询服务,计算总资产
项目地址springcloud-contract-consumer-rest
项目结构 验证服务