首页
学习
活动
专区
工具
TVP
发布

微服务环境下的集成测试探索(二)——契约式测试

作者 章烨明

杏仁医生CTO。中老年程序员,关注各种技术和团队管理。

微服务的集成消费者驱动契约熟悉敏捷开发的同学应该知道,敏捷开发提倡测试先行,相应的提出了不少方法和流程,例如测试驱动开发(Test Driven Design,TDD)、验收测试驱动开发(Acceptance Test Driven Development,ATDD)、行为驱动设计(Behavior Driven Design,BDD )、实例化需求(Specification By Example)等等。它们的共同特点在开发前就约定好了各种形式的契约。如果是单元测试作为契约,就是 TDD;如果是验收测试作为契约,就是 ATDD;如果是形式化语言甚至图表定义的业务规则,那就是 BDD 或者实例化需求。对于基于 HTTP 的微服务来说,它的契约就是指 API 的请求和响应的规则。对于请求,包括请求 URL 及参数,请求头,请求内容等;对于响应,包括状态码,响应头,响应内容等。在 Spring Cloud Contract 里,契约是用一种基于 Groovy 的 DSL 定义的。例如下面是一个短信接口的契约(省略了部分内容,例如头等)。

org.springframework.cloud.contract.spec.Contract.make{// 如果消费方发送了一个请求request{// 请求方法是 POSTmethod'POST'// 请求 URL 是 `/sendsms`url'/sendsms'// 请求内容是 Json 文本,包括电话号码和要发送的文本body([// 电话号码必须是13个数字组成phone:$(regex('[0-9]')),


// 发送文本必须为"您好"content:"您好"])}response{// 那么服务方应该返回状态码 200status200// 响应内容是 Json 文本,内容为 { "success": true }body([success:true])}}

使用 CDC 开发服务的大致过程是这样的。

业务方和服务方相关人员一起讨论。业务方告知服务方接口使用的场景、期望的返回是什么,服务方考虑接口方案和实现,双方一起定下一个或多个契约。

确定了契约之后,Spring Cloud Contract 会给服务方自动生成验收测试,用于验证接口是否符合契约。服务方要确保开发完成后,这些验收测试都能够通过。

业务方也可以基于这个契约开始开发功能。Spring Cloud Contract 会基于契约生成 Stub 服务,这样业务方就不必等接口开发完成,可以通过 Stub 服务进行集成测试。

所以 CDC 和行为驱动设计(BDD)很类似,都是从使用者的需求出发,双方订立契约,测试先行的开发方法。不过一个是针对系统的验收,一个是针对服务的集成。CDC 的好处有以下几点:

让服务方和调用方有充分的沟通,确保服务方提供接口都是以调用方的需求出发,并且服务方的开发者也可以充分理解调用方的使用场景。

解耦和服务方和调用方的开发过程,一旦契约订立,双方都可以并行开发,通过 Mock 和自动化集成测试确保双方都遵守契约,最终集成也会更简单。

通过 Mock 和自动化测试,可以确保双方在演进过程中,也不会破坏已有的契约。

但是要注意一点是,契约不包括业务逻辑,业务逻辑还是需要服务方和调用方通过单元测试、其他集成测试来确保。例如上面的短信服务,可能服务方会有一个逻辑是每天一个号码最多发送一条短信,但这个逻辑并不会包含在契约里,可能契约只有包含成功和错误两种情况。Spring Cloud Contract 使用方法

服务方Spring Cloud Contract 支持 Gradle 和 Maven,详细的配置文档就不细述了,请参考文档。对于服务方,Spring Cloud Contract 提供了一个叫 Contarct Verifier 的东西,用于解析契约文件生成测试。如果使用 Gradle 的话,通过以下命令生成测试。

上面发送短信的契约,生成的测试代码是这样的。

publicclassSmsTestextendsContractBase{

@Testpublicvoidvalidate_sendsms()throwsException{// given:MockMvcRequestSpecificationrequest=given().body("{\"phone\":\"2066260255168\",\"content\":\"\u60A8\u597D\"}");// when:ResponseOptionsresponse=given().spec(request).post("/sendsms");// then:assertThat(response.statusCode()).isEqualTo(200);// and:DocumentContextparsedJson=JsonPath.parse(response.getBody().asString());assertThatJson(parsedJson).field("['success']").isEqualTo(true);}

}

可以看到是一个很标准的 JUnit 测试,使用了 RestAssured 来测试 API 接口。其中的 ContractBase 是设置的测试基类,里面可以做一些配置以及 Setup 和 Teardown 操作。例如这里,我们需要用 RestAssured 来启动 Spring 的 webApplicationContext,当然我也可以用 standaloneSetup 设置启动单个 Controller。

调用方首先我们需要在服务方通过以下命令生成 Stub 服务的 Jar 包。

这个 Jar 包里面包含了契约文件以及生成的 WireMock 映射文件。我们可以把它发布到 Maven 私库里去,这样调用方可以直接从私库下载 Stub 的 Jar 包。对于调用方,Spring Cloud Contract 提供了 Stub Runner 来简化 Stub 的使用。

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.NONE)

@AutoConfigureStubRunner(repositoryRoot="http://",ids={"com.xingren.service:sms-client-stubs:1.5.0-SNAPSHOT:stubs:6565"})

publicclassContractTest{@TestpublicvoidtestSendSms(){ResponseEntityresponse=restTemplate.exchange("http://localhost:6565/sendsms",HttpMethod.POST,newHttpEntity(request),SmsServiceResponse.class);// do some verification}

}

注意注解 AutoConfigureStubRunner,里面设置了下载 Stub Jar 包的私库地址以及包的完整 ID,注意最后的 6565 就是指定 Stub 运行的本地端口。测试的时候访问 Stub 端口,就会根据契约返回内容。前端开发另外一个使用 Mock 的场景就是对于前端开发。以前,前端工程师一般需要自己创建 Mock 数据进行开发,但 Mock 数据很容易和后台最终提供的数据有不一致的地方。CDC 和 Spring Cloud Contract 也可以帮上忙。Spring Cloud Contract 生成的 Stub 其实是 WireMock 的映射文件,因此直接使用 WireMock 也是可以的。不过,它还提供了使用 Spring Cloud Cli 运行 Stub 的方式。首先需要安装 SpringBoot Cli 和 Spring Cloud Cli,Mac 下可以使用 Homebrew

然后在当前目录创建一个 stubrunner.yml 配置文件,里面的配置参数和前面的 AutoConfigureStubRunner 的配置其实是一样的:

最后运行,即可启动 Stub 服务。前端同学就可以愉快的使用 Stub 来进行前端开发了。DSLSpring Cloud Contract 的契约 DSL,既可以用于生成服务方的测试,也可以用于生成供调用方使用的 Stub,但是这两种方式对数据的验证方法有一些不同。对于服务方测试,DSL 需要提供请求内容,验证响应;而对于 Stub,DSL 需要匹配请求,提供响应内容。Spring Cloud Contract 提供了几种方式来处理。一种方式是通过的语法(或者、、,都是一样的)。例如。

org.springframework.cloud.contract.spec.Contract.make{

request{method('GET')url$(consumer(~/\/[-9]{2}/), producer('/12'))

}

response {

status 200

body(

name: $(consumer('Kowalsky'), producer(regex('[a-zA-Z]+'))))}

}

上面就是指对于调用方,url 需要匹配这个正则表达式,Stub 就会返回响应,其中 name 则为。而对于服务方,生产的测试用例的请求 url 为,它会验证响应中的 name 符合正则。另外,Spring Cloud Contract 还提供了和来支持更复杂的请求匹配和测试验证。Spring Cloud Contract 现在还在快速发展中,目前对于生成测试用例的规则,还是有不够灵活的地方。例如,对于某些 Stub 应该返回,但生成的测试里不需要验证的字段,支持不太完善。还有对于的请求,处理起来不如 Json 的请求那么方便。相信后继版本会改善。总结通过上面简单介绍,我们可以看到基于 Spring Cloud Contract 以及契约测试的方法,可以让微服务之间以及前后端之间的集成更顺畅。另外前面还提到 Pact,它的优势是支持多种语言,但我们的环境都是基于 JVM 的,而 Spring Cloud Contract 和 SpringBoot 以及 Junit 的集成更简单方便。而且 Spring Cloud Contract 的另一个优势是它可以自动生成服务方的自动化测试。

全文完

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180110G0OS3B00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券