Spring Cloud Contract 微服务契约测试

简介

使用场景

主要用于在微服务架构下作CDC(消费者驱动契约)测试。下图展现了多个微服务的调用,若是咱们更改了一个模块要如何进行测试呢?java

  • 传统的两种测试思路
    • 模拟生产环境部署全部的微服务,而后进行测试
      • 优势
        • 测试结果可信度高
      • 缺点
        • 测试成本太大,装一整套环境耗时,耗力,耗机器
    • Mock其余微服务作端到端的测试
      • 优势
        • 不用装整套产品了,测的也方便快捷
      • 缺点
        • 须要写不少服务的Mock,要维护一大堆不一样版本用途的simulate(模拟器),一样耗时耗力
  • Spring Cloud Contrct解决思路
    • 每一个服务都生产可被验证的 Stub Runner,经过WireMock调用,服务双方签定契约,一方变化就更新本身的Stub,而且测对方的Stub。Stub其实只提供了数据,也就是契约,能够很轻量的模拟服务的请求返回。而Mock可在Stub的基础上增长验证

契约测试流程

  • 服务提供者
    • 编写契约,能够用Groovy DSL 脚本也能够用 YAML文件
    • 编写测试基类用于构建过程当中插件自动生成测试用例
    • 生成的测试用例会自动运行,这时若是我么提供的服务不能知足契约中的规则就会失败
    • 提供者不断完善功能直到服务知足契约要求
    • 发布Jar包,同时将Stub后缀的jar一同发布
  • 服务消费者
    • 对须要依赖外部服务的接口编写测试用例
    • 经过注解指定须要依赖服务的Stub jar包
    • 验证外部服务没有问题

简单案例

服务提供者

模拟一个股票价格查询的服务git

项目地址

springcloud-contract-provider-restgithub

项目结构

项目依赖

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

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-contract-maven-plugin</artifactId>
      <version>2.2.1.RELEASE</version>
      <extensions>true</extensions>
      <configuration>
        <!--用于构建过程当中插件自动生成测试用例的基类-->
        <baseClassForTests>
          com.example.springcloudcontractproviderrest.RestBaseCase
        </baseClassForTests>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

编写契约

既然是消费者驱动契约,我么首先须要制定契约,这里为了方便假设查询贵州茅台的股价返回值是固定的999,也能够经过正则等方式去限制返回值spring

Contract.make {
    description "query by id should return stock(id,price)"

    request {
        method GET()
        url value {
            // 消费者使用时请求任何 /stock/price/数字 都会被转为 /stock/price/600519
            consumer regex('/stock/price/\\d+')
            producer "/stock/price/600519"
        }
    }

    response {
        status OK()
        headers {
            contentType applicationJson()
        }
        // 提供给消费者的默认返回
        body([
                id   : 600519,
                price: 999
        ])

        // 服务端在测试过程当中,body须要知足的规则
        bodyMatchers {
            jsonPath '$.id', byRegex(number())
            jsonPath '$.price', byRegex(number())
        }
    }
}

测试基类

主要是加载环境,而后因为不是真实环境模拟了数据库查询数据库

@SpringBootTest
@RunWith(SpringRunner.class)
public class RestBaseCase {

    @Autowired
    private StockController stockController;

    @MockBean
    private StockRepository stockRepository;

    @Before
    public void setup() {
        init();
        RestAssuredMockMvc.standaloneSetup(stockController);
    }

    private void init() {
        Mockito.when(stockRepository.getStockById(600519)).thenReturn(new StockDTO(600519, "贵州茅台", 999L, "SH"));
    }

}

实现服务并测试

实现咱们的服务功能,具体代码逻辑能够在项目地址中查看,而后测试看是否符合契约json

mvn clean test架构

能够在生成(target)目录中找到 generated-test-sources 这个目录,插件为咱们自动生成而且运行的case就在其中app

public class StockTest extends RestBaseCase {

    @Test
    public void validate_shoudReturnStockIdAndPrice() throws Exception {
        // given:
            MockMvcRequestSpecification request = given();


        // when:
            ResponseOptions response = given().spec(request)
                    .get("/stock/price/600519");

        // then:
            assertThat(response.statusCode()).isEqualTo(200);
            assertThat(response.header("Content-Type")).matches("application/json.*");

        // and:
            DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());

        // and:
            assertThat(parsedJson.read("$.id", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
            assertThat(parsedJson.read("$.price", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
    }

}

发布

若是一切顺利就能够deploy了maven

服务消费者

模拟查询我的资产的服务,须要远程调用股票价格查询服务,计算总资产ide

项目地址

springcloud-contract-consumer-rest

项目结构

验证服务

编写测试用例验证服务

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(
        ids = {"com.example:springcloud-contract-provider-rest:+:stubs:8880"},
        stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
public class StockApiTest {

    @Autowired
    private StockApi stockApi;

    @Test
    public void testStockApi() throws IOException {
        StockPriceDTO stockPrice = stockApi.getStockPrice(600519).execute().body();
        BDDAssertions.then(stockPrice.getId()).isEqualTo(600519);
        BDDAssertions.then(stockPrice.getPrice()).isEqualTo(999);

    }
}
相关文章
相关标签/搜索