一、背景
1、概述(来着官网的一段介绍)
Spring Cloud Contract 是一个包含解决方案的总括项目,可帮助用户成功实施消费者驱动的合同方法。目前 Spring Cloud Contract 由 Spring Cloud Contract Verifier 项目组成。
Spring Cloud Contract Verifier 是一种工具,它支持基于 JVM 的应用程序的消费者驱动契约 (CDC) 开发。它附带了用 Groovy 或 YAML 编写的合同定义语言 (DSL)。
2、自我理解:
简单点来说就是提前设置一个已知结果,消费者调用时直接返回该结果,该结果在消费者的程序处理中是否正确,来测试消费者程序。
3、解决了哪些痛点
目前很多系统都采用了微服务的开发模式,随着项目规模的不断扩大,服务数量越来越多,服务之间的依赖也越来越密切。不同的团队负责不同的服务开发,其中某个服务发生变动,很多服务也是需要进行修改,这将需要大量的沟通成本和代码修改时间成本。
4、目前常见测试中:
单元测试无法测试服务间的连接或接口调用正确性。
API接口测试是针对业务接口进行,主要是测试接口实现是否完整,且需要依赖具体真实服务。
集成测试无疑是最佳的,但是需要依赖具体真实服务,且各个服务都需要同时进行测试,其效率很慢。
契约测试,无需依赖具体真实服务,测试服务间的连接或接口调用正确性 。测试效率快。
总体来说,契约测试是一个介于单元测试和集成测试的一个阶段,他关注的细粒度比单元测试更粗,但是又无法取代集成测试。
二、实现
目前官网和很多博客文章都介绍了,远程库使用契约测试的方法。我这边就介绍一下本地库实现方法。基本一致,只不过读取jar是读取本地。
服务提供者
在服务端编写一个简单的接口,判断数字是否为负数。
@RestController
public class NumberController {
@GetMapping("/value/number")
public String isMinus(@RequestParam("number") Integer number) {
return number < 0 ? "true" : "false";
}
}
pom文件
<testFramework>JUNIT</testFramework>,不加这个可能无法进行install。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>3.1.1</version>
<extensions>true</extensions>
<configuration>
<testFramework>JUNIT</testFramework>
<baseClassForTests>com.lin.test.BaseTestClass</baseClassForTests>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.12.RELEASE</version>
<configuration>
<mainClass> com.lin.test.MainApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
接下来需要新建基础测试类以及存根,注意存放目录,如图所示:
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseTestClass {
@Autowired
private NumberController numberController;
@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(numberController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}
存根(groovy文件)
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return true when number input is minus"
request {
method GET()
url("/value/number") {
queryParameters {
parameter("number", "-1")
}
}
}
response {
body("true")
status 200
}
}
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return false when number input not minus"
request {
method GET()
url("/value/number") {
queryParameters {
parameter("number", "1")
}
}
}
response {
body("false")
status 200
}
}
到此服务提供者编写完成,我们接下来进行mvn clean install 就会生成测试类
构建还会在我们的本地Maven存储库中添加存根jar。
服务消费者
@RestController
public class BasicMathController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/calculate")
public String checkOddAndEven(@RequestParam("number") Integer number) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
ResponseEntity<String> responseEntity = restTemplate.exchange(
"http://localhost:8090/value/number?number=" + number, HttpMethod.GET,
new HttpEntity<>(httpHeaders), String.class);
return responseEntity.getBody();
}
}
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>provider-test</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.12.RELEASE</version>
<configuration>
<!--指定启动类-->
<mainClass> com.lin.test.MainApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
存根运行器
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(ids = {"org.example:provider-test:+:stubs:8090"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class BasicMathControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void given_WhenPassEvenNumberInQueryParam_ThenReturnEven() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/calculate?number=2").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(content().string("Even"));
}
}
@AutoConfigureStubRunner这个注解非常重要,这里选择的是本地存根。 之前以为这样就完事了,调用时报错了。找不到对应maven存储库,找到了默认的。
需要进行设置参数:
org.apache.maven.user-settings
到此结束,文章有些粗糙,有哪里不对的,欢迎指正。