Spring Cloud Contract 契约测试实践
本文转载公众号:永辉云创技术
该号由我参与维护,欢迎大家关注支持!!!
分布式研发模型演进
众所周知, 分布式系统是由众多微服务构成,并按照功能模块划分后, 由不同的开发小组进行维护. 研发模型如下图所示: 开发人员完成某一个微服务的功能后, 发布测试环境交付测试团队验证. 这种工作模式的弊端是, Bug在测试环境才被暴露, 而不是在编码阶段就被发现.
为了解决上述的弊端, 研发团队通常会引入了单元测试, 并使用EasyMock, Mokito等框架, 来帮助开发人员在开发阶段暴露Bug. (对DB, Redis等依赖通常使用Docker来解决, 与主题无关, 这里暂时不做过多介绍. 有兴趣的可以自己研究)
在日常的研发工作中, 很多团队或多或少遇到过这种情形: 微服务提供方修改了对外接口, 导致消费方无法正常请求, 造成生产事故. 管理上的人为避免, 难免导致各种疏漏, 为此我们找到了一种智能的解决方案---消费者驱动的契约测试. 大意是这样的: 服务提供方和消费方约定共同的契约, 双方围绕契约, 进行各自的单元测试工作.
Spring Cloud Contract概要
永辉云创使用Spring Cloud作为微服务基础框架, 借助Spring Cloud Contract来帮助服务提供方和消费方来制定契约. 所谓契约, 就是双方约定好的接口调用参数, 及对应的输出. 整体概览如下图所示.
通过上图, 相信大家对Spring Cloud Contract有了大体的了解, 下面我们用几个关键词来描述Spring Cloud Contract的特性.
用于UT
定义远程服务数据
自动生成测试代码
Spring Cloud Contract在永辉云创的具体实施步骤如下图所示, 通常, 服务提供方, 也是数据定义方. 在这里, 我们使用的了数据定义方(所有服务契约在一个工程中定义), 服务提供方, 服务消费方三方模型.
Spring Cloud Contract实践
以下内容,摘自我们推进Spring Cloud Contract落地之初,编写的技术文档。 希望给读者带来更加接地气的参考, 部分内容进行了脱敏, 请读者谅解.
数据定义方
对于请求返回数据, 所有提供方统一在spring-cloud-contract(内部项目名, 非spring cloud Contract)项目里定义, 方便大家看测试数据
原则上由服务开发定义者来提供这个groovy,但是如果时间急迫,依赖方直接编写,并有服务开发者review后也可以提交~
题外话:有些工具, 例如wiremock可以帮助录制并模拟http请求. 使用场景: 前端开发依赖于服务端提供的接口, 我们通常是等服务端开发完成后,部署到测试环境,供前端调用. 现在有了wiremock, 假设我们要开发v2版本的接口, 可以先录制v1版本的请求, 然后修改胶片为v2版本http响应. 这样就可以前端就可以在v2接口开发完成前, 愉快地进行mock请求, 减少前端对服务端接口进度的依赖.*
http://www.cnblogs.com/tanglang/p/4791198.html
http://wiremock.org/docs/running-standalone/
服务提供方
引入UT相关jar包
<!-- 集成wireMock来实现mock请求响应。wireMock会自动构建一个虚拟远程服务 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <scope>test</scope></dependency>
<!-- 提供打包预定义数据服务 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope></dependency>
<!-- 自动生成单元测试代码 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-verifier</artifactId> <scope>test</scope></dependency>
<!-- 依赖数据定义方 --><dependency> <groupId>com.yonghui</groupId> <artifactId>spring-cloud-contract</artifactId> <version>1.0-SNAPSHOT</version></dependency>
配置UT代码生成器插件
该插件可以帮助我们生成自动化代码, 执行命令"mvn clean install -Dmaven.test.skip=false"后, 即可看到target目录自动生成的UT代码. 注意, 插件要>1.1.4.RELEASE, (该版本修复了long类型的dsl生成测试代码报错的问题)
<!-- UT代码生成器插件 --><plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>1.1.4.RELEASE</version> <extensions>true</extensions> <configuration> <!-- packageWithBaseClasses 设置基类包目录,使用baseClassMappings替代,不使用 --> <!--<packageWithBaseClasses>contract</packageWithBaseClasses>--> <!--baseClassMappings 设置生成测试的基类。用包名的正则来进行匹配 --> <contractsWorkOffline>true</contractsWorkOffline> <baseClassMappings> <baseClassMapping> <contractPackageRegex>.*</contractPackageRegex> <baseClassFQN>contract.ContractBase</baseClassFQN> </baseClassMapping> </baseClassMappings> <!--basePackageForTests 设置测试的生成的位置 --> <basePackageForTests>verifier.tests</basePackageForTests> <contractDependency> <groupId>com.yonghui</groupId> <artifactId>spring-cloud-contract</artifactId> <version>1.0-SNAPSHOT</version> <classifier>stubs</classifier> </contractDependency> <!--contractsPath 设置contracts路径--> <contractsPath>contracts/xxx-mst-center</contractsPath> </configuration> <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.4.12</version> </dependency> <dependency> <groupId>com.yonghui</groupId> <artifactId>spring-cloud-contract</artifactId> <version>1.0-SNAPSHOT</version> <classifier>stubs</classifier> </dependency> </dependencies></plugin>
配置UT基础类
生成UT代码时, 有需求是需要初始化数据库, 配置内置的redis, mysql. 我们使用相关的开源框架, 搭建了自己的UT基础类, 进行ut前的场景准备.
package contract.resources;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;import com.yonghui.junit.InmomeryDbResource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.context.WebApplicationContext;
/** * Created by luyunfei on 09/10/2017. */public class LocationDbResource extends InmomeryDbResource {
public LocationDbResource() { // 初始化内置mysql, UT执行时, 会使用flyway进行初始化相关的表 super(40200, "xxx_mst_center"); }
@Override protected void before() throws Throwable { super.before(); // 初始化这个UT msql的相关数据 runResourceFile(dbName, "sql/contract/mst_location.sql"); }}
package contract;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;import com.yonghui.junit.RedisResource;import com.yonghui.xxx.mst.center.api.impl.TestBootstrap;import contract.resources.LocationDbResource;import org.junit.Before;import org.junit.ClassRule;import org.junit.rules.ExternalResource;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.web.context.WebApplicationContext;
/** * Created by luyunfei on 27/09/2017. */@RunWith(SpringRunner.class)@SpringBootTest(classes = {TestBootstrap.class})public class xxx_mst_centerFLocationServiceBase {
@Autowired private WebApplicationContext context;
// 增加这一行即可在UT中引入内置mysql, 并执行初始化 @ClassRule public static final ExternalResource dbresource = new LocationDbResource();
// 增加这一行即可在UT中引入内置Redis @ClassRule public static final ExternalResource resource = new RedisResource(20300);
@Before public void setUp() throws Throwable {// RestAssuredMockMvc.standaloneSetup(new AccountController()); RestAssuredMockMvc.webAppContextSetup(context); }}
Test文件夹下的项目启动类Bootstrap
需要注释掉consul, feign, 保证ut对外部依赖的隔离. 经过实践, 发现测试时TestBootstrap不会覆盖Bootstarp, 因此需要保持两者名字一致, 即TestBootstrap要修改文件名为Bootstrap.class
package com.yonghui.xxx.mst.center.api.impl;
import org.mybatis.spring.annotation.MapperScan;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.cloud.client.SpringCloudApplication;import org.springframework.context.annotation.ComponentScan;
/** * Created by luyunfei on 11/04/2017. */@EnableAutoConfiguration// 注意不要用SpringCloudApplication, 它会依赖consul启动, 而ut中不需要启动consul//@SpringCloudApplication@SpringBootApplication// 下面这个要注释掉, 其它和Bootstrap一样// @Import({YhConsulConfig.class,FeignConfiguration.class})// 需要引入FeignConfiguration.class, 同时增加配置spring.application.feature.enabled=false@Import({FeignConfiguration.class})@ComponentScan(basePackages = "com.yonghui.xxx")@MapperScan("com.yonghui.xxx.mst.center.mapper")public class Bootstrap {
private static final Logger log = LoggerFactory.getLogger(TestBootstrap.class);
public static void main(String[] args) { SpringApplication.run(TestBootstrap.class, args); log.info("Bootstrap started successfully"); }
}
test/resources/bootstrap.properties__增加配置(重要) __
spring.cloud.consul.enabled=falsespring.application.feature.enabled=false
服务消费方
配置和服务提供方一致, 需要调用提供方接口的测试类, 增加以下注释, 端口号不要写错了
@AutoConfigureStubRunner(ids = {"com.yonghui:xxx-mst-center-server:1.0-SNAPSHOT:stubs:5656"} ,workOffline = true)
package contract;
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;import com.yonghui.junit.InmomeryDbResource;import com.yonghui.junit.RedisResource;import com.yonghui.xxx.inventory.center.api.impl.TestBootstrap;import org.junit.Before;import org.junit.ClassRule;import org.junit.rules.ExternalResource;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.web.context.WebApplicationContext;
/** * Created by luyunfei on 28/09/2017. */
@RunWith(SpringRunner.class)@SpringBootTest(classes = {TestBootstrap.class})@AutoConfigureStubRunner(ids = {"com.yonghui:xxx-mst-center-server:1.0-SNAPSHOT:stubs:5656"} ,workOffline = true)public class Xxx_inventory_centerFInventoryServiceBase extends InmomeryDbResource {
@Autowired private WebApplicationContext context;
@ClassRule public static final ExternalResource resource = new RedisResource(20300);
public xxx_inventory_centerFInventoryServiceBase() { super(40200, "xxx_inventory_center"); }
@Before public void setup() throws Throwable {// RestAssuredMockMvc.standaloneSetup(new AccountController()); RestAssuredMockMvc.webAppContextSetup(context); super.before(); // 初始化sql //runResourceFile(dbName, "sql/DockServiceImplTest01/dockServiceGetListTest01.sql"); }
}
热文推荐:
全球最大同性交友网站 GitHub 10 岁了!
JDK 1.5 - 1.8 各版本的新特性总结
Spring Boot快速开发利器:Spring Boot CLI
IntelliJ IDEA 2018.1正式发布!还能这么玩?
消息中间件选型分析
自建API网关「架构设计篇」
其他推荐:
Spring Cloud构建微服务架构:分布式配置中心(加密解密)
Spring Boot使用@Async实现异步调用:线程池的优雅关闭
Spring Boot使用@Async实现异步调用:自定义线程池
Spring Boot 2.0正式发布,升还是不升呢?
Spring Boot 2.0 新特性概览
Spring Boot/Cloud干货汇总
长按指纹
一键关注
深入交流、更多福利
扫码加入我的知识星球
点击 “阅读原文” 看看本号其他精彩内容
Spring Cloud Contract 契约测试实践相关推荐
- 消费者驱动的微服务契约测试套件:Spring Cloud Contract
在微服务架构下,你的服务可能由不同的团队提供和维护,在这种情况下,接口的开发和维护可能会带来一些问题,比如服务端调整架构或接口调整而对消费者不透明,导致接口调用失败. 为解决这些问题,Ian Robi ...
- 消费者驱动的微服务契约测试套件Spring Cloud Contract
在微服务架构下,你的服务可能由不同的团队提供和维护,在这种情况下,接口的开发和维护可能会带来一些问题,比如服务端调整架构或接口调整而对消费者不透明,导致接口调用失败. 为解决这些问题,Ian Robi ...
- 消费者驱动的契约测试 Spring Cloud Contract介绍
消费者驱动的契约测试 Spring Cloud Contract介绍 什么是契约测试 测试是软件流程中非常重要,不可或缺的一个环节.一般的测试分为单元测试,集成测试,端到端的手工测试,这也是构成测试金 ...
- Spring Cloud Contract实践
1.Spring Cloud Contract简介 Spring Cloud Contract是一个总体项目,其中包含帮助用户成功实施消费者驱动合同方法的解决方案.目前,Spring Cloud Co ...
- 契约测试之Spring Cloud Contract
在微服务架构下,服务间会通过某种形式的消息传递或API调用进行耦合,这让服务的集成以及测试变成了非常具有挑战的一件事.早在微服务流行之前,就有人提出了消费者驱动契约(Consumer-driven c ...
- Spring系列学习之Spring Cloud Contract测试消息传递
英文原文:https://spring.io/projects/spring-cloud-contract 目录 概述 特性 Spring Boot配置 Server / Producer方面 Cli ...
- spring cloud contract的应用实现与概念理解-服务请求者一侧的落地-细节较多避免踩坑卡壳
笔者的经验认为,微服务的出现,是为了应对传统SOA架构在多服务背景下的疲软,本质上是SOA的进一步衍生.是一种治理服务的手段.而微服务之所以能够解决传统SOA.单块大单体程序的问题,原因在于微服务自身 ...
- spring cloud contract的应用实现与概念理解-服务提供者一侧的落地
如题,本文是在前一篇"spring cloud contract的应用实现与概念理解-服务请求者一侧的落地"的基础上,续写服务提供者一侧的有关实现与理解. 通过对官网文章的学习和编 ...
- Marcin Grzejszczak访谈:Spring Cloud Contract
Marcin Grzejszczak是Pivotal的一名软件工程师.目前,他在从事Spring Cloud Contract的开发,这是一个消费者驱动的.面向Java的契约框架.为了了解该框架的一些 ...
最新文章
- RabbitMQ 延迟队列实现定时任务的正确姿势,你学会了么?
- Git学习系列(二)创建本地仓库及文件操作
- 无法上外网, ping网关ping不通的解决——arp命令
- unittest单元测试框架总结
- 抑制恐慌,互联网能够做些什么?
- python学习笔记--turtle库的使用
- [Java基础]Collections概述和使用
- C语言2020年作业,2020年c语言上机报告范文【四篇】
- 中国最闷声发大财的城市,人均GDP超杭州
- 宁德时代在川签署四方协议 加快全省锂矿资源勘查开发
- janusgraph整合mysql_图解图库JanusGraph系列-一文知晓“图数据“底层存储结构
- java 读取 tgz_java – 从Spark中的压缩中读取整个文本文件
- 在Angular中添加第三方库jQuery、bootstrap
- 2 抽象工厂模式(Abstract Factory)
- 病毒是什么?地球上万物都是相生相克的,那么病毒在自然界的天敌是什么?...
- 手风琴控件android,ExpandableListView实现手风琴效果
- C#窗体程序随电脑分辨率自动调整大小
- 科普类:什么是量子霸权?
- 概率笔记5——概率分布
- 不同操作系统中的换行符,回车符 \r , \n