单元测试、组件测试和集成测试的一个共同特点是,会将应用的某一部分隔离开来去测试,而不是测试整个完整的应用。对于单元测试,被测单元只有一个或者很少几个类 ;对于集成测试,你在应用的边界测试应用是否可以连接到一个真实的服务。在微服务架构下,你的服务可能由不同的团队提供和维护,在这种情况下,接口的开发和维护可能会带来一些问题,比如服务端调整架构或接口调整而对消费者不透明,导致接口调用失败。为解决这些问题,Ian Robinson提出了一个以服务消费者定义契约为驱动的开发模式:“Consumer-Driver Contracts(CDC)”,就是:消费者驱动契约
消费者驱动的契约测试(Consumer-Driven Contracts,简称CDC),是指从消费者业务实现的角度出发,驱动出契约,再基于契约,对提供者验证的一种测试方式。
这里就假设有provider团队和consumer团队。那么当provider团队的服务还没有开发好,或者provider的团队的服务没有在启动的时候,我们可不可以进行开发呢?
答案是可以的。
这里就假设有provider团队和consumer团队。那么当provider团队的服务还没有开发好,或者provider的团队的服务没有在启动的时候,我们可不可以进行开发呢? ->可以的。
这里引入一个重要的概念,就是契约,Contract。这是什么呢?很简单,就是provider和consumer事先要约定好一个接口的规范,之后双方提供服务接口和消费服务接口都要按照这个契约来。
Spring Cloud Contract 提供不错的实现,它分为验证服务(Verifier)和对契约内容Mock服务(Stub Runner)两部分。
契约使用步骤可大致分为:
- 生产者提供定义好的契约(接口,包含request Method、header,parameter,body)
- 生产者生成stub jar,提供给消费者,可mvn install到Maven库(本地/远程仓库)
- 消费者调用生产者的接口(从契约获取数据)
现在我们开始编写我们的契约测试,首先我们来处理生产者这一方。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-contract-verifierartifactId>
<scope>testscope>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-contract-maven-pluginartifactId>
<version>2.0.2.RELEASEversion>
<extensions>trueextensions>
<configuration>
<baseClassForTests>com.cc.cloud.contract.OrderBasebaseClassForTests>
configuration>
plugin>
plugins>
build>
路径:src\test\java\com\cc\cloud\contract\OrderBase.java
package com.cc.cloud.contract;
import com.cc.cloud.order.controller.OrderController;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
public class OrderBase {
@Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(new OrderController());
}
}
OrderController如下:
package com.cc.cloud.order.controller;
import com.cc.cloud.order.feign.MemberFeign;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RefreshScope
@RestController
@RequestMapping("/order")
public class OrderController {
private final Logger logger = LoggerFactory.getLogger(OrderController.class);
private MemberFeign memberFeign;
@Value("${cloud.service.order}")
private String orderConfig;
@Autowired
public void setMemberFeign(MemberFeign memberFeign) {
this.memberFeign = memberFeign;
}
@RequestMapping("/members")
@ResponseStatus(HttpStatus.OK)
public List<String> getMemberList() {
return memberFeign.getAllMemberList();
}
@GetMapping("/orders")
@ResponseStatus(HttpStatus.OK)
public List<String> getOrders() {
List<String> orders = Lists.newArrayList();
orders.add("order 1");
orders.add("order 2");
return orders;
}
@GetMapping("/config")
@ResponseStatus(HttpStatus.OK)
public String getOrderConfig(){
return orderConfig;
}
}
增加契约文件,可以有多种格式如groovy 、yaml,这里采用yaml。
契约默认位置:src/test/resources/contracts
路径: src\test\resources\contracts\should_return_order_list.yml
request:
method: GET
url: /order/orders
response:
status: 200
headers:
Content-Type: application/json;charset=UTF-8
body:
["order 1","order 2"]
mvn clean install
当然也可以用Maven插件工具去生成。
当然如果服务提供方没有完成接口的开发,可以用下面的命令去提供stubs-jar
mvn clean install -DskipTests
C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building cloud-service-order 1.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ cloud-service-order ---
[INFO] Deleting C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ cloud-service-order ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ cloud-service-order ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\classes
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.0.2.RELEASE:generateTests (default-generateTests) @ cloud-service-order ---
[INFO] Generating server tests source code for Spring Cloud Contract Verifier contract verification
[INFO] Will use contracts provided in the folder [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\src\test\resources\contracts]
[INFO] Directory with contract is present at [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\src\test\resources\contracts]
[INFO] Test Source directory: C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\generated-test-sources\contracts added.
[INFO] Using [com.cc.cloud.contract.OrderBase] as base class for test classes, [null] as base package for tests, [null] as package with base classes, base class mapping
s []
[INFO] Creating new class file [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\generated-test-sources\contracts\com\cc\cloud\contract\ContractVerifierT
est.java]
[INFO] Generated 1 test classes.
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.0.2.RELEASE:convert (default-convert) @ cloud-service-order ---
[INFO] Will use contracts provided in the folder [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\src\test\resources\contracts]
[INFO] Directory with contract is present at [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\src\test\resources\contracts]
[INFO] Copying Spring Cloud Contract Verifier contracts to [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\stubs\META-INF\com.cc.cloud\cloud-service-or
der\1.0\contracts]. Only files matching [.*] pattern will end up in the final JAR with stubs.
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] ignoreDelta true
[INFO] Copying 1 resource
[INFO] Copying file should_return_order_list.yml
[INFO] Converting from Spring Cloud Contract Verifier contracts to WireMock stubs mappings
[INFO] Spring Cloud Contract Verifier contracts directory: C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\src\test\resources\contracts
[INFO] Stub Server stubs mappings directory: C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\stubs\META-INF\com.cc.cloud\cloud-service-order\1.0\mapping
s
[INFO] Creating new stub [C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\stubs\META-INF\com.cc.cloud\cloud-service-order\1.0\mappings\should_return_ord
er_list.json]
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ cloud-service-order ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ cloud-service-order ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ cloud-service-order ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.cc.cloud.contract.ContractVerifierTest
14:50:51.177 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - 3 mappings in <unknown>
14:50:51.881 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Log4j2LoggerProvider
14:50:51.885 [main] INFO org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 6.0.13.Final
14:50:51.909 [main] DEBUG org.hibernate.validator.internal.engine.resolver.TraversableResolvers - Cannot find javax.persistence.Persistence on classpath. Assuming non J
PA 2 environment. All properties will per default be traversable.
14:50:51.945 [main] DEBUG org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator - Loaded expression factory via original TCCL
14:50:51.946 [main] DEBUG org.hibernate.validator.internal.engine.ConfigurationImpl - Setting custom MessageInterpolator of type org.springframework.validation.beanvali
dation.LocaleContextMessageInterpolator
14:50:51.948 [main] DEBUG org.hibernate.validator.internal.engine.ConfigurationImpl - Setting custom ParameterNameProvider of type org.springframework.validation.beanva
lidation.LocalValidatorFactoryBean$1
14:50:51.955 [main] DEBUG org.hibernate.validator.internal.xml.config.ValidationXmlParser - Trying to load META-INF/validation.xml for XML based Validator configuration
.
14:50:51.957 [main] DEBUG org.hibernate.validator.internal.xml.config.ResourceLoaderHelper - Trying to load META-INF/validation.xml via TCCL
14:50:51.958 [main] DEBUG org.hibernate.validator.internal.xml.config.ResourceLoaderHelper - Trying to load META-INF/validation.xml via Hibernate Validator's class load
er
14:50:51.959 [main] DEBUG org.hibernate.validator.internal.xml.config.ValidationXmlParser - No META-INF/validation.xml found. Using annotation based configuration only.
14:50:52.250 [main] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.springframework.validation.beanvalidation.LocaleContextMess
ageInterpolator as ValidatorFactory-scoped message interpolator.
14:50:52.251 [main] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.hibernate.validator.internal.engine.resolver.TraverseAllTra
versableResolver as ValidatorFactory-scoped traversable resolver.
14:50:52.253 [main] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.hibernate.validator.internal.util.ExecutableParameterNamePr
ovider as ValidatorFactory-scoped parameter name provider.
14:50:52.253 [main] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.hibernate.validator.internal.engine.DefaultClockProvider as
ValidatorFactory-scoped clock provider.
14:50:52.254 [main] DEBUG org.hibernate.validator.internal.engine.ValidatorFactoryImpl - HV000234: Using org.hibernate.validator.internal.engine.scripting.DefaultScript
EvaluatorFactory as ValidatorFactory-scoped script evaluator factory.
14:50:52.357 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter - ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder,
1 RequestBodyAdvice, 1 ResponseBodyAdvice
14:50:52.468 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: 0 @ExceptionHandler, 1 Respo
nseBodyAdvice
14:50:52.538 [main] INFO org.springframework.mock.web.MockServletContext - Initializing Spring TestDispatcherServlet ''
14:50:52.538 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Initializing Servlet ''
14:50:52.549 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected AcceptHeaderLocaleResolver
14:50:52.550 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected FixedThemeResolver
14:50:52.550 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@
210f0cc1
14:50:52.550 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Detected org.springframework.web.servlet.support.SessionFlashMapManager@19542407
14:50:52.551 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be maske
d to prevent unsafe logging of potentially sensitive data
14:50:52.553 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Completed initialization in 15 ms
14:50:52.716 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - GET "/order/orders", parameters={}
14:50:52.727 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to public java.util.List com.cc.
cloud.order.controller.OrderController.getOrders()
14:50:52.799 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using ' application/json', given [*/*] and supported
[application/json, application/*+json]
14:50:52.800 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [[order 1, order 2]]
14:50:52.825 [main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - Completed 200 OK
14:50:53.190 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $[?]
14:50:53.199 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.211 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.211 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $[?]
14:50:53.211 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.211 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.211 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $[?]
14:50:53.212 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.212 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.213 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: $[?]
14:50:53.213 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
14:50:53.220 [main] DEBUG com.jayway.jsonpath.internal.path.CompiledPath - Evaluating path: @
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.549 s - in com.cc.cloud.contract.ContractVerifierTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.0.2.RELEASE:generateStubs (default-generateStubs) @ cloud-service-order ---
[INFO] Files matching this pattern will be excluded from stubs generation []
[INFO] Building jar: C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\cloud-service-order-1.0-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:3.1.0:jar (default-jar) @ cloud-service-order ---
[INFO] Building jar: C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\cloud-service-order-1.0.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.1.RELEASE:repackage (repackage) @ cloud-service-order ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ cloud-service-order ---
[INFO] Installing C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\cloud-service-order-1.0.jar to D:\Maven\repository\com\cc\cloud\cloud-service-order\1.
0\cloud-service-order-1.0.jar
[INFO] Installing C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\pom.xml to D:\Maven\repository\com\cc\cloud\cloud-service-order\1.0\cloud-service-order-1.0.p
om
[INFO] Installing C:\Users\c\Desktop\spring-cloud-demo\cloud-service-order\target\cloud-service-order-1.0-stubs.jar to D:\Maven\repository\com\cc\cloud\cloud-service-or
der\1.0\cloud-service-order-1.0-stubs.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.086 s
[INFO] Finished at: 2019-11-10T14:50:54+08:00
[INFO] Final Memory: 90M/674M
[INFO] ------------------------------------------------------------------------
存根jar已安装到本地Maven仓库,可提供给其他服务调用,后面会调用到。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-contract-stub-runnerartifactId>
<scope>testscope>
dependency>
package com.cc.cloud.member.controller;
import com.cc.cloud.test.common.ControllerTestBase;
import org.junit.Test;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.http.MediaType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
//@AutoConfigureStubRunner自动下载存根
@AutoConfigureStubRunner(ids = {
"com.cc.cloud:cloud-service-order:+:stubs:8095"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class MemberControllerContractTest extends ControllerTestBase {
@Test
public void should_return_status_isOk_when_call_api_members_given_get_method() throws Exception {
this.getMockMvc()
.perform(
get("/member/orders")
.contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
}
}
契约测试的流程就已经完成了。
我这里只是简单介绍了怎么编写契约测试,有兴趣的可以参考下面的链接。
感谢下面的链接提供了学习的资料,感谢~~
消费者驱动的微服务契约测试套件:Spring Cloud Contract
Spring Cloud Contract 契约测试实践
基于Feign的微服务调用之契约测试 Spring Cloud Contract
Spring Cloud Contract 契约测试
消费者驱动的微服务契约测试套件Spring Cloud Contract
Spring Cloud Contract
Spring-Cloud-Contract实战
契约测试:微服务完整应用系统验证之道
Spring Cloud Contract in a polyglot world
就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers
https://gitee.com/cckevincyh/spring-cloud-demo/tree/spring-cloud-contract/