TA的每日心情 | 擦汗 4 小时前 |
---|
签到天数: 1047 天 连续签到: 5 天 [LV.10]测试总司令
|
微服务体系测试的难点
微服务中存在大量的进程间通信,通信方式可能是同步调用,也可能是使用消息组件进行异步通信。
一对服务之间的交互称作两个服务的契约,比如,支付服务和账单服务之间就需要在通信用的消息结构体上达成一致,同理,网关和下游服务就需要在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 contractsimport org.springframework.cloud.contract.spec.ContractContract.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 { [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仓库下载)。
- @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的名称。
|
|