51Testing软件测试论坛

标题: 为什么说契约测试是微服务体系测试的重中之重? [打印本页]

作者: lsekfe    时间: 2021-2-5 09:53
标题: 为什么说契约测试是微服务体系测试的重中之重?
微服务体系测试的难点
  微服务中存在大量的进程间通信,通信方式可能是同步调用,也可能是使用消息组件进行异步通信。
  一对服务之间的交互称作两个服务的契约,比如,支付服务和账单服务之间就需要在通信用的消息结构体上达成一致,同理,网关和下游服务就需要在REST接口上达成一致,也因此开发人员需要保证自己负责的服务有稳定的API。
  要验证两个服务的交互,常见的方法就是运行两个服务,然后调用通信API,看看是否符合预期。但这往往会遇到集成的问题,并且会涉及到端到端,测试过程要尽可能避免端到端的测试。为此,契约测试需要由消费者来驱动。
  消费者驱动的契约测试
  假设现在有一个网关,有一个下游的用户服务,网关用来调用用户服务的查询用户信息的接口,为此需要编写测试来验证网关和用户服务能够正常交互。这个场景中,网关是消费者,用户服务是生产者,消费者驱动的契约测试就是它的生产者的集成测试,用来验证查询用户信息的接口是否符合网关的期望。
  那么在这个场景中如何才能证明生产者接口符合消费者期望呢?
  接口具备预期的HTTP请求方法(GET,POST,PUT,DELETE....)和路径
  带有预期的Request Header(假设存在)
  符合预期的Request Body(假设存在)
  返回了预期的Response Status/Body/Headers。。。
  注意:契约测试不会涉及业务逻辑的测试
  团队的协作方式如下图:

  契约测试组件一般是一些对应API请求的示例。
  缺少CDC的结果
  最常见的情况就是,原本在线上运行的好好的服务,在其他某个服务更新后出现了问题
  而作为生产者的服务,需要修改接口的时候,不知道到底有哪些消费者在消费自己的接口,而且一旦开始通过调用链路寻找,很可能会牵扯出一大片的服务,导致不敢修改
  Spring Cloud Contract的使用
  Spring-Cloud-Contract是Spring提供的一个契约测试框架,下面举个使用的例子:
  我是网关的开发者,我现在要为用户服务编写一个消费者契约测试,流程如下图。我编写的契约定义了网关如何与用户服务进行交互,用户服务的开发小组使用这些契约来测试用户服务,而我则用它们来测试网关。

  我编写一个或者多个契约,每个契约里面有且仅有一个网关可能发送给用户服务的HTTP请求,和一个预期的HTTP响应,我通过git Pull Request的方式将契约交付给用户服务的开发小组
  用户服务的开发小组使用消费者契约测试用户服务,测试代码由Spring-Cloud-Contract从契约代码中生成
  用户服务开发组将用于测试用户服务的契约发布到Maven仓库
  我使用已发布契约为网关进行测试
  因为我使用的是已经发布的契约来对网关进行测试,因此我可以确信它能够跟部署的用户服务一起协作。
  一个契约差不多长这样,我是用Groovy DSL写的,也可以用YAML来写
  1. package contracts​import org.springframework.cloud.contract.spec.Contract​Contract.make {    request {        method 'POST'        urlPath('/user/getUser')    }    response {        status 200        body(                '''                {    "name": "Default User"}                '''        )        headers {            header('Content-Type', 'application/json')        }    }}
复制代码
 install之后就会生成类似下面的代码,还有STUB的jar包(我这里生成的是client-a-server-1.0-SNAPSHOT-stubs.jar)等,具体可自行尝试。
  1. @SuppressWarnings("rawtypes")public class ContractVerifierTest extends ContractVerifierBase {​   [url=home.php?mod=space&uid=724]@test[/url]   public void validate_getUserInfo() throws Exception {      // given:         MockMvcRequestSpecification request = given();​​      // when:         ResponseOptions response = given().spec(request)               .post("/user/getUser");​      // then:         assertThat(response.statusCode()).isEqualTo(200);         assertThat(response.header("Content-Type")).isEqualTo("application/json");​      // and:         DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());         assertThatJson(parsedJson).field("['name']").isEqualTo("Default User");   }​}
复制代码
接下来就去消费者服务中编写测试调用的代码,在没有开启A服务的情况下,该测试方法一样可以执行成功(我在这边用的是CLASSPATH的方式引入stub包,实际生产中应用REMOTE,从远程Maven仓库下载)。
  1. @RunWith(SpringRunner.class)@SpringBootTest(classes = ClientB.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@AutoConfigureMockMvc@AutoConfigureJsonTesters@Slf4j@AutoConfigureStubRunner(ids = {"org.example:client-a-server:1.0:stubs"}, stubsMode = StubRunnerProperties.StubsMode.CLASSPATH)public class ClientBTest {​    @Autowired    private ClientAFeignClient feignClient;​    @Test    public void testGetUser() {        ResponseEntity<UserDTO> dtoResponseEntity = feignClient.getUser();        log.info("Entity={}", dtoResponseEntity);    }}
复制代码
由于是Feign调用,因此还要加上配置。
  1. stubrunner.ids-to-service-ids.client-a-server=Client-A
复制代码
 client-a-server是A服务的artifactId,Client-A是Feign-Client的名称。







欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2