【Spring Cloud 基础设施搭建系列】Spring Cloud Demo项目 Spring Cloud Contract契约测试

Spring Cloud Contract契约测试

单元测试、组件测试和集成测试的一个共同特点是,会将应用的某一部分隔离开来去测试,而不是测试整个完整的应用。对于单元测试,被测单元只有一个或者很少几个类 ;对于集成测试,你在应用的边界测试应用是否可以连接到一个真实的服务。在微服务架构下,你的服务可能由不同的团队提供和维护,在这种情况下,接口的开发和维护可能会带来一些问题,比如服务端调整架构或接口调整而对消费者不透明,导致接口调用失败。为解决这些问题,Ian Robinson提出了一个以服务消费者定义契约为驱动的开发模式:“Consumer-Driver Contracts(CDC)”,就是:消费者驱动契约

消费者驱动的契约测试(Consumer-Driven Contracts,简称CDC),是指从消费者业务实现的角度出发,驱动出契约,再基于契约,对提供者验证的一种测试方式。

这里就假设有provider团队和consumer团队。那么当provider团队的服务还没有开发好,或者provider的团队的服务没有在启动的时候,我们可不可以进行开发呢?

答案是可以的。

契约(Contract)

这里就假设有provider团队和consumer团队。那么当provider团队的服务还没有开发好,或者provider的团队的服务没有在启动的时候,我们可不可以进行开发呢? ->可以的。

这里引入一个重要的概念,就是契约,Contract。这是什么呢?很简单,就是provider和consumer事先要约定好一个接口的规范,之后双方提供服务接口和消费服务接口都要按照这个契约来。

Spring Cloud Contract 提供不错的实现,它分为验证服务(Verifier)和对契约内容Mock服务(Stub Runner)两部分。

契约使用步骤可大致分为:

  1. 生产者提供定义好的契约(接口,包含request Method、header,parameter,body)
  2. 生产者生成stub jar,提供给消费者,可mvn install到Maven库(本地/远程仓库)
  3. 消费者调用生产者的接口(从契约获取数据)

生产者(服务提供方)提供定义好的契约

现在我们开始编写我们的契约测试,首先我们来处理生产者这一方。

  1. 首先我们先添加依赖还有插件。我们的生产者也就是服务的提供方是order服务,所以我们需要在cloud-service-order服务下添加依赖和插件。
<dependency>
    <!-- 自动生成单元测试代码 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>
<build>
    <plugins>
        <plugin>
            <!--该插件自动生成测试类、stubs(存根)-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-maven-plugin</artifactId>
            <!--使用2.1.0.RELEASE会出现org.springframework.cloud.contract.verifier.util.ContractVerifierUtil找不到的问题-->
            <!--解决方案替换成2.0.2.RELEASE-->
            <version>2.0.2.RELEASE</version>
            <extensions>true</extensions>
            <configuration>
                <baseClassForTests>com.cc.cloud.contract.OrderBase</baseClassForTests>
            </configuration>
        </plugin>
    </plugins>
</build>
  1. 创建插件指定的测试基类OrderBase

路径: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;
    }
}

  1. 编写契约

增加契约文件,可以有多种格式如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"]
  1. 服务提供方进行契约测试并生成stubs-jar
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<java.lang.String> 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仓库,可提供给其他服务调用,后面会调用到。

消费者(服务调用方)使用存根

  1. 这里的服务调用方式member服务,所以我们需要在cloud-service-member中添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <scope>test</scope>
</dependency>
  1. 编写测试类
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());
    }
}
  1. 运行测试

在这里插入图片描述

契约测试的流程就已经完成了。

我这里只是简单介绍了怎么编写契约测试,有兴趣的可以参考下面的链接。

参考

感谢下面的链接提供了学习的资料,感谢~~

消费者驱动的微服务契约测试套件: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/

发布了647 篇原创文章 · 获赞 816 · 访问量 98万+

猜你喜欢

转载自blog.csdn.net/cckevincyh/article/details/102997874