什么是微服务契约测试?这些难点解析总结给你

发表于:2023-1-30 09:23

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:LLLLimbo    来源:掘金

  微服务体系测试的难点
  微服务中存在大量的进程间通信,通信方式可能是同步调用,也可能是使用消息组件进行异步通信。
  一对服务之间的交互称作两个服务的契约,比如,支付服务和账单服务之间就需要在通信用的消息结构体上达成一致,同理,网关和下游服务就需要在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来写。
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)等,具体可自行尝试。
@SuppressWarnings("rawtypes")public class ContractVerifierTest extends ContractVerifierBase {​   @Test   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仓库下载)。
@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调用,因此还要加上配置。
stubrunner.ids-to-service-ids.client-a-server=Client-A
  client-a-server是A服务的artifactId,Client-A是Feign-Client的名称。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号