# spring-cloud-contract-sample1 **Repository Path**: conkeyn/spring-cloud-contract-sample1 ## Basic Information - **Project Name**: spring-cloud-contract-sample1 - **Description**: Spring cloud contract的示例 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-06-03 - **Last Updated**: 2020-12-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # spring-cloud-contract-sample1 #### 为什么需要消费者契约? 随着系统拓扑的增长,测试微服务成为一项艰巨的任务。当微服务器链接在一起以实现业务功能时,通过编写集成测试来验证他们正在一起工作是很有挑战性的。如果您沿着这条路径走下去,您将需要拥有所有的应用程序,基础资源(如数据库,S3存储区)和第三方API在已知状态下连接并运行,以确保“服务A(作为消费者)”可以通话到“服务B(作为生产人)”。 事实上这是麻烦的设置不是唯一的问题在这里。你的测试有时可能会神秘地失败。“有时”可能意味着各种薄片,如网络超时,第三方API速率限制,或仅仅是从以前的测试运行留下的数据。如果您只想测试一个微服务器的API,则管理所有这些移动部件太多了。 幸运的是,可以使用像Hoverfly或WireMock这样的服务虚拟化工具来嘲笑依赖的服务。测试服务A和B之间的集成成为服务A的隔离组件测试,其中嵌入了一个服务B。 然而,这又造成了另一个困境:您如何保证服务B的存根始终跟踪实际服务的更改?想象一下,在服务B工作的开发人员悄悄地推出一个API更新,使服务A使用的存根无效,并且连续的部署管道为基于服务A通过测试的发布提供了绿灯。这最终会导致生产中的消防。 也许现在是考虑两个服务之间的协议的时候了。服务A(作为消费者)创建一个服务B(作为生产人)必须遵守的契约。这种契约作为服务之间的隐形粘合剂 - 尽管它们分别独立于代码库并运行在不同的JVM上。在构建时可以立即检测到变化。 这被称为消费者驱动契约(CDC)测试,这是在分布式架构中测试服务虚拟化的有效方式。下面将介绍Spring Cloud Contract:基于JVM的项目的CDC框架,特别是使用Spring Boot的项目。 #### 一个简单的用例 在这个演示中,我们有两个微服务器:订阅(作为消费者)和帐户(作为生产人)。我们需要为订阅服务添加新功能,以便对朋友的帐户进行订阅是免费的。要查明帐户是否标记为“朋友”,订阅服务需要使用帐户服务的“按ID获取帐户”API。 ![契约关系图](http://images-kojpv4yu.oss-cn-beijing.aliyuncs.com/others/F5A263AF-C19E-40ed-AD32-D988570FFE67.png) #### 需要依赖 1. Java 2. Spring boot 3. Spring cloud contract 4. Maven 关键依赖关系是spring-cloud-starter-contract-verifier 生产者自动生成API验证测试,spring-cloud-starter-stub-runner 消费者自动配置存根服务器。 #### 分步工作流程 CDC测试类似于架构/ API级别的TDD,因此共享类似的工作流程。 添加测试: 在消费者(subscription-service)方面,我们首先编写新功能的功能测试,并实现与生产者端点通信的网关。 ```java @RunWith(SpringRunner.class) @SpringBootTest public class SubscriptionTest { @Autowired private SubscriptionService service; @Test public void shouldGiveFreeSubscriptionForFriends() throws Exception { // given: String accountId = "12345"; Subscription subscription = new Subscription(accountId, MONTHLY); // when: Invoice invoice = service.createInvoice(subscription); // then: assertThat(invoice.getPaymentDue()).isEqualTo(0); assertThat(invoice.getClientEmail()).isNotEmpty(); } } ``` 运行所有测试(运行test方法或者执行“mvn test”命 ): 显然它们失败了 ```java org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8082/account/12345": Connection refused. ``` 在account-service使用Spring Cloud Contract Groovy DSL添加消费者契约代码。spring-cloud-contract-maven-plugin默认去src/test/resources/contracts的目录下寻找.groovy文件 ```groovy package contracts import org.springframework.cloud.contract.spec.Contract Contract.make { request { method 'GET' url value(consumer(regex('/account/[0-9]{5}')), producer('/account/12345')) } response { status 200 body([ type: 'friends', email: 'tom@api.io' ]) headers { header('Content-Type': value( producer(regex('application/json.*')), consumer('application/json') )) } } } ``` 消费者契约包括请求和响应对。它显示了使用URL路径的动态值的示例。使用value(consumer(...),producer(...))辅助方法,可以设置匹配器或具体value。 如图1所示,value(consumer(...),producer(...))中 1. consumer()是针对consumer端消费者契约会使用stubs模拟出一个服务端出来验证comsumer的请求值或向consumer端发出响应值; 2. producer()是针对producer端消费者契约会使用verifier模拟一个client向producer发出请求值,或者验证producer端返回的响应; 在account-service生成消费者契约的测试代码 ```bash mvn spring-cloud-contract:generateTests mvn spring-cloud-contract:generateStubs ``` 如下图2所示, 1. 箭头1所指就是通过消费者契约groovy生成的java单元测试代码,可用于调试。 2. 箭头2所指是生成的stub,特别是mappings可用于放到wiremock中独立运行。 ![代码位置图](http://images-kojpv4yu.oss-cn-beijing.aliyuncs.com/others/234204FE-5855-4039-B3FE-567C4289FC68.png) 生成的代码 ```java public class ContractVerifierTest extends ContractVerifierBase { @Test public void validate_shouldReturnFriendsAccount() throws Exception { // given: MockMvcRequestSpecification request = given(); // when: ResponseOptions response = given().spec(request) .get("/account/12345"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['email']").isEqualTo("tom@api.io"); assertThatJson(parsedJson).field("['type']").isEqualTo("friends"); } } ``` 如果生成的无法运行,那么就需要为它配置环境。以下演示IDEA环境的配置示例 ![Modules配置图](http://images-kojpv4yu.oss-cn-beijing.aliyuncs.com/others/7D895B77-AE65-499c-9BBC-4CEEF6AFCA60.png) 此时会发现生成的代码中缺少继承的父类ContractVerifierBase,我们需要在src/test/java目录下继续添加此类。 ```java package com.demo.account.contracts; @Ignore @RunWith(SpringRunner.class) @SpringBootTest(classes = AccountServiceApplication.class) public class ContractVerifierBase { @Autowired private WebApplicationContext context; @Before public void setUp() throws Exception { RestAssuredMockMvc.webAppContextSetup(context); } } ``` 我们还需要在account-service的pom.xml文件中的配置spring-cloud-contract-maven-plugin。指定ContractVerifierBase所在包名 ```xml org.springframework.cloud spring-cloud-contract-maven-plugin com.demo.account.contracts ``` 现在添加account-service的controller及service来通过测试。 ```java @RestController public class AccountController { @Autowired private final AccountService accountService; @RequestMapping(method = RequestMethod.GET, value = "/account/{id}") public Account getAccount(@PathVariable String id) { return accountService.getById(id); } } @Service public class AccountService { public Account getById(String id) { return new Account(id, "friends", "tom@api.io"); } } ``` 再次运行ContractVerifierTest.validate_shouldReturnFriendsAccount()将验证通过。 那么,接下来以运行mvn clean install将生并发布wiremock映射文件(图2所示的stubs/mappings目录下的文件)到本地仓库。 最后,还需要在消费端(subscription-service)的测试类SubscriptionTest添加注解 。 ```java @AutoConfigureStubRunner(ids = "com.demo:account-service:+:stubs:8082", workOffline = true) ``` workOffline=true表示从本地库中读取stubs.jar文件。如果为false那么会从远程仓中读取。 ids是依赖stubs.jar的规则"groupid:artifactId:version:stubs:port"。其中+号是指最新版本。如果不指端口号,那么会随机生成一个端口号。