600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > spring cloud contract的应用实现与概念理解-服务提供者一侧的落地

spring cloud contract的应用实现与概念理解-服务提供者一侧的落地

时间:2022-11-07 06:13:58

相关推荐

spring cloud contract的应用实现与概念理解-服务提供者一侧的落地

如题,本文是在前一篇“spring cloud contract的应用实现与概念理解-服务请求者一侧的落地”的基础上,续写服务提供者一侧的有关实现与理解。

通过对官网文章的学习和编程实践的验证,contract的应用实现,实际上并不须要在同一个工程中同时完成stub桩(给服务消费者用)和mock模拟(给服务提供者用)。经常看到的例子中,既包含“spring-cloud-starter-contract-verifier"又包含“spring-cloud-starter-contract-stub-runner”(POM.XML文件中配置依赖),那也许是因为微服务中,很多工程实际上既是另外某个服务消费者,也是其他服务或者客户端的服务提供者。如果把问题分解,单独从服务提供者的角度而言,实际上更简单。

集成测试的背景、意义,contract在集成测试中的位置和作用,在我的前一篇文章spring cloud contract的应用实现与概念理解-服务请求者一侧的落地-细节较多避免踩坑卡壳已经进行了比较感性的描述,本文就不再累述。下面就直接从服务提供者一侧开发的目的、过程和内容来记述。首先,服务提供者利用contract的目的抛开给消费者提供确定的接口规格桩外,对于服务提供者自身,是形成一个测试发起者的模拟(mock),向协议脚本中定义的服务URL(REST API如此,消息队列是另外的参数,不在本文中深究)提交预期定好的请求,服务提供者此时的服务实现应该已经完成开发并能响应,contract将会在测试方法中对响应进行比对验证,所有要素的符合预期的话,则达到了验证服务有效性、正确性的目的。

服务提供者一侧contract的编辑和实现,实际上很简单。具体步骤其实,在前一篇文章中已经提到了。此时,首先由服务消费者和服务提供者设计讨论服务的接口规格,然后由服务提供者在版本中POM.XML增加contract的插件配置,再针对要测试的服务接口编辑contract脚本文件就可以了。就这么简单。过程如下图:后面将会解析其背后的逻辑。

过程和内容而言,很简单。

首先,在POM中增加(假设第一次在这个工程中开发contract测试)

首先,增加spring cloud的版本定义,因为contract实际上是spring cloud的组件

<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud-release.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

其中,spring cloud 和 contract的版本配置如下(9月 经过实践验证,可用)

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<spring-cloud-release.version>.0.3</spring-cloud-release.version>

<spring-cloud-contract.version>3.1.3</spring-cloud-contract.version>

</properties>

然后,加入contract构建插件,这个插件的作用在于依据contract脚本生成stub桩。

在build--plugins下增加

<plugin><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-contract-maven-plugin</artifactId><version>${spring-cloud-contract.version}</version><extensions>true</extensions><configuration><!-- <packageWithBaseClasses>.contract-sample</packageWithBaseClasses> --><baseClassForTests>.contract_sample.BaseTestClass</baseClassForTests></configuration><!-- if additional dependencies are needed e.g. for Pact --><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-contract-pact</artifactId><version>${spring-cloud-contract.version}</version></dependency></dependencies></plugin>

设定此插件的同时,按照官网的说法,服务提供者一侧的有关依赖就也包含在里面了。不过,我在具体案例中,还是加入了verifier依赖,如下:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-contract-verifier</artifactId><scope>test</scope></dependency>

值得专门说明的地方,在“spring-cloud-contract-maven-plugin”中,有一行<baseClassForTests>.contract_sample.BaseTestClass</baseClassForTests>目的在于告知contract“引擎”将该BaseTestClass类作为所有contract测试类的基类。后续的编码中,由于服务提供者需要对自己的服务进行测试,所以是需要开发人员编写这个类(BaseTestClass),但实际上不需要在该类中实现测试方法。只需要进行一个测试服务的加载即可(本案例中服务入口的controller是FraudController),至于为什么不需要测试方法,后面会介绍。代码如下:

package .contract_sample;import .contract_sample.controller.FraudController;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.extension.ExtendWith;import org.springframework.boot.test.context.SpringBootTest;import io.restassured.module.mockmvc.RestAssuredMockMvc;import lombok.extern.slf4j.Slf4j;import org.springframework.test.context.junit.jupiter.SpringExtension;@ExtendWith(SpringExtension.class)@SpringBootTest(classes = .contract_sample.App.class)@Slf4jpublic class BaseTestClass {@BeforeEachpublic void setup() {RestAssuredMockMvc.standaloneSetup(new FraudController());}}

接下来,就是依据协商好的接口编制contract脚本,脚本的位置应该在src\test\resources的contracts目录(自建)中,contract脚本名无所谓,可以自己取。比如“service_first_sample.groovy”

脚本内容如下:

package contractsorg.springframework.cloud.contract.spec.Contract.make {request {method 'PUT'url '/fraudcheck'body(["client_id": $(regex('[0-9]{10}')),loanAmount: 99999])headers {contentType('application/json')}}response {status OK()body([checkStuats: "FRAUD","reason": "Amount too high"])headers {contentType('application/json')}}}

到此,测试框架和测试用例的开发就完成了。不过测试肯定通过不了,因为被测试的服务本身没有完成。

服务的代码后续会附上,这里先说明一下为什么不需要开发专门针对具体服务的测试类。这是因为contract框架在工程的install处理时,会自动前面定义的基类创建一个contract测试类,类名是ContractVerifierTest,这个类的JAVA文件是自动产生的,将会在target目录中,而不是开发者的src目录中。该类将依据contract脚本,自行生成对应的测试请求和响应对于的测试方法加入的该测试类中。并在工程install时自动执行测试。测试的内容就是依据契约模拟请求,向服务发送请求,并验证每一个响应元素。服务实现没有完成或者响应不符合预期都会断言失败,相应的如果没有断言失败则说明测试通过。比如本案例中,ContractVerifierTest将产生一个测试方法如下:

@Testpublic void validate_service_first_sample() throws Exception {// given:MockMvcRequestSpecification request = given().header("Content-Type", "application/json").body("{\"client_id\":\"7375534648\",\"loanAmount\":99999}");// when:ResponseOptions response = given().spec(request).put("/fraudcheck");// 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("['checkStuats']").isEqualTo("FRAUD");assertThatJson(parsedJson).field("['reason']").isEqualTo("Amount too high");}

从方法名到方法内容,都是contract框架自动产生的。所以,就服务而言,开发者根本不需要额外编辑测试方法和测试类。如果想自己开发其他更复杂的测试,也可以。

从服务提供者的角度而言,针对想测试的服务,将相应的contract脚本文件编辑好就可以了,都会产生响应的测试方法到该ContractVerifierTest类中或者其基类中,不需要额外编辑其他测试类。就是这么简单。

这样的话,服务提供者就不需要服务请求者开发好来在实际集成环境中来测试,而是自己就可以进行测试了。

附:本案例服务的代码如下:

controller类

package .contract_sample.controller;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import lombok.extern.slf4j.Slf4j;import .contract_sample.entity.LoanRequest;@RestController@Slf4jpublic class FraudController {@PutMapping(value = "/fraudcheck", consumes="application/json", produces="application/json")public String check(@RequestBody LoanRequest loanRequest) { log.info("请求的参数是:{}",loanRequest);if (loanRequest.getLoanAmount() > 10000) { return "[{checkStuats: FRAUD, reason: Amount too high}]"; } else {return "[{checkStuats: OK, reason: Amount OK}]"; }}}

请求对象:

package .contract_sample.entity;import com.fasterxml.jackson.annotation.JsonProperty;public class LoanRequest {@JsonProperty("client.id")private String clientId;private Long loanAmount;public String getClientId() {return clientId;}public void setClientId(String clientId) {this.clientId = clientId;}public Long getLoanAmount() {return loanAmount;}public void setLoanRequestAmount(Long loanAmount) {this.loanAmount = loanAmount;}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。