文章目录
这里契约测试基于Spring Cloud Contract来编写,大致流程如下所示:
- 在Provider使用groovy DSL编写Contract
- 通过Contract Verifier验证Contract所生成的测试
- 测试通过后将Artifact(stub.jar)发布到Maven仓库中
- 在Consumer端pull下相应的Artifact(stub.jar)
- 运行测试,同时以Artifact作为基础设施启动Stub server
- 在测试中向Stub server发送请求验证API的正确性
Provider
添加gradle插件和依赖
buildscript {
ext {
springBootVersion = '2.0.5.RELEASE'
springColudContractVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
// ......
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.springframework.cloud:spring-cloud-contract-gradle-plugin:${springColudContractVersion}")
// ......
}
}
// ......
apply plugin: 'groovy'
apply plugin: 'spring-cloud-contract'
// ......
dependencies {
// ......
testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
// ......
}
编写Contract
按照Spring Cloud Contract的约定,groovy编写的DSL文件需要在在src/test/resources/contracts/目录下。
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
url "/goods"
method GET()
}
response {
status 200
headers {
contentType applicationJson()
}
body '''
[{
"id" : 1,
"name" : "123",
},
{
"id" : 2,
"name" : "456",
},
{
"id" : 3,
"name" : "789",
}]
'''
}
}
创建测试基类
给从Contract生成的测试类指定一个基类。按照Spring Cloud Contract的约定基类以Base结尾。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = GoodsServiceApplication.class)
public class ContractVerifierBase {
@Autowired
private WebApplicationContext applicationContext;
@Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(applicationContext);
//RestAssuredMockMvc.standaloneSetup(goodsController); 也可以使用standaloneSetup启动单个controller。
}
}
在gradle指定gradle plugin使用的基类
contracts {
packageWithBaseClasses = 'contracts'
}
这样会到test/java/contracts目录中找以Base结尾到基类。按照Spring Cloud Contract的,如果groovy dsl文件在目录src/test/resources/contract/foo/bar/baz/中,而且packageWithBaseClasses
设置为com.example.base,则会到com.example.base目录中寻找名为BarBazBase的基类。也就是说会以最后两级目录名作为基类名字。
运行测试
./gradlew generateContractTests // 根据Contract在build/generated-test-sources/contracts/目录下生成测试类
./gradlew test //运行测试
发布Artifact
在测试通过之后需要将stub jar包发布到consumer可以获取到的地方,在这里发布到本地的maven仓库。
publishing {
publications {
stubs(MavenPublication) {
groupId 'com.learning'
artifactId "goods-service"
version '0.0.1'
artifact verifierStubsJar
}
}
repositories {
mavenLocal()
}
}
Consumer
在Provider对契约进行验证并将stub.jar发布在maven仓库中后,需要在Consumer对契约进行验证。
添加gradle插件和依赖
ext {
springBootVersion = '2.0.5.RELEASE'
springColudContractVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
// ......
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.springframework.cloud:spring-cloud-contract-gradle-plugin:${springColudContractVersion}")
// ......
}
}
// ......
dependencies {
// ......
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
// ......
}
配置stub runner
在application-contract-test.yml文件中添加stub runner配置
stubrunner:
stubsMode: local
ids:
- com.learning:goods-service:0.0.1:stubs
stubsMode: 表示stub是在本地获取还是remote获取。这里指定为local,在本地maven仓库找stub。
ids: 指定所要获取对stub。
当然还有其他配置,有兴趣对可以自己去研究。
添加测试代码
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApiGatewayApplication.class)
@AutoConfigureStubRunner
@ActiveProfiles("contract-test")
public class GoodsClientTest {
@Autowired
private StubFinder stubFinder;
@Test
public void should_return_correct_goods() {
int port = stubFinder.findStubUrl("com.learning", "goods-service").getPort();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost:" + port + "/goods", List.class);
List responseBody = responseEntity.getBody();
assertEquals(3,responseBody.size());
}
}
在这里使用StubFinder进行测试。当请求/goods时,会将请求转发到stub中并返回指定到契约。这样可以保证provider是按照契约返回数据到consumer中。
如果不使用StubFinder,还可以基于FeignClient去请求。