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

微服务集成测试:契约式测试实践
本文深入探讨了微服务环境下如何进行集成测试,重点关注契约式测试的方法和优势。通过契约式测试,可以确保服务间的接口一致性,增强系统的稳定性和可靠性。

作者 | 章烨明

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

微服务的集成

前一篇已经提到,传统方式下,微服务的集成以及测试都是一件很头痛的事情。其实在微服务概念还没有出现之前,在 SOA 流行的时候,就有人提出了消费者驱动契约(Consumer Driven Contract,CDC)的概念。微服务流行后,服务的集成和集成测试成了不得不解决问题,于是出现了基于消费者驱动契约的测试工具,最流行的应该就是 Pact,还有就是今天我们要说的 Spring Cloud Contract。

消费者驱动契约

熟悉敏捷开发的同学应该知道,敏捷开发提倡测试先行,相应的提出了不少方法和流程,例如测试驱动开发(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 定义的。例如下面是一个短信接口的契约(省略了部分内容,例如 Content-Type 头等)。

org.springframework.cloud.contract.spec.Contract.make {
    // 如果消费方发送了一个请求
    request {                 
        // 请求方法是 POST               
        method 'POST'    
        // 请求 URL 是 `/sendsms`                   
        url '/sendsms'       
        // 请求内容是 Json 文本,包括电话号码和要发送的文本                
        body([         
               // 电话号码必须是13个数字组成                      
               phone: $(regex('[0-9]{13}')),                 
// 发送文本必须为"您好" content: "您好" ]) } response { // 那么服务方应该返回状态码 200 status 200 // 响应内容是 Json 文本,内容为 { "success": true }                   body([ success: true ]) }}


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

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

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

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

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

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

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

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

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

Spring Cloud Contract 使用方法

服务方

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

如果使用 Gradle 的话,通过以下命令生成测试。

./gradlew generateContractTests

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

public class SmsTest extends ContractBase {
   @Test public void validate_sendsms() throws Exception { // given: MockMvcRequestSpecification request = given() .body("{\"phone\":\"2066260255168\",\"content\":\"\u60A8\u597D\"}"); // when: ResponseOptions response = given().spec(request) .post("/sendsms"); // then: assertThat(response.statusCode()).isEqualTo(200); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['success']").isEqualTo(true); }
}

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

@Before
public void setup() {
    RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}

调用方

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

./gradlew verifierStubsJar

这个 Jar 包里面包含了契约文件以及生成的 WireMock 映射文件。我们可以把它发布到 Maven 私库里去,这样调用方可以直接从私库下载 Stub 的 Jar 包。

对于调用方,Spring Cloud Contract 提供了 Stub Runner 来简化 Stub 的使用。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
@AutoConfigureStubRunner(repositoryRoot="http://<nexus_root>", ids = {"com.xingren.service:sms-client-stubs:1.5.0-SNAPSHOT:stubs:6565"})
public class ContractTest { @Test public void testSendSms() { ResponseEntity<SmsServiceResponse> response = restTemplate.exchange("http://localhost:6565/sendsms", HttpMethod.POST, new HttpEntity<>(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

$ brew tap pivotal/tap
$ brew install springboot
$ spring install org.springframework.cloud:spring-cloud-cli:1.4.0.RELEASE

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

stubrunner:
  workOffline: false
  repositoryRoot: http://<nexus_root>
  ids:
    - com.xingren.service:sms-client-stubs:1.5.0-SNAPSHOT:stubs:6565

最后运行 spring cloud stubrunner,即可启动 Stub 服务。前端同学就可以愉快的使用 Stub 来进行前端开发了。

DSL

Spring Cloud Contract 的契约 DSL,既可以用于生成服务方的测试,也可以用于生成供调用方使用的 Stub,但是这两种方式对数据的验证方法有一些不同。对于服务方测试,DSL 需要提供请求内容,验证响应;而对于 Stub,DSL 需要匹配请求,提供响应内容。Spring Cloud Contract 提供了几种方式来处理。

一种方式是通过 $(consumer(...), producer(...)) 的语法(或者$(stub(...), test(...))$(client(...), server(...))$(c(...), p(...)),都是一样的)。例如。

org.springframework.cloud.contract.spec.Contract.make {
   request
{ method('GET') url $(consumer(~/\/[0-9]{2}/), producer('/12'))    
     
 
   
response {        
       
status 200        
       
body(                
           name: $(consumer('
Kowalsky'), producer(regex('[a-zA-Z]+'))) ) }
}

上面就是指对于调用方,url 需要匹配 ~/\/[0-9]{2}/ 这个正则表达式,Stub 就会返回响应,其中 name 则为 Kowalsky。而对于服务方,生产的测试用例的请求 url 为 /12,它会验证响应中的 name 符合正则 '[a-zA-Z]+'。另外,Spring Cloud Contract 还提供了 stubMatchers  testMatchers 来支持更复杂的请求匹配和测试验证。

Spring Cloud Contract 现在还在快速发展中,目前对于生成测试用例的规则,还是有不够灵活的地方。例如,对于某些 Stub 应该返回,但生成的测试里不需要验证的字段,支持不太完善。还有对于 form-urlencoded 的请求,处理起来不如 Json 的请求那么方便。相信后继版本会改善。

总结

通过上面简单介绍,我们可以看到基于 Spring Cloud Contract 以及契约测试的方法,可以让微服务之间以及前后端之间的集成更顺畅。

另外前面还提到 Pact,它的优势是支持多种语言,但我们的环境都是基于 JVM 的,而 Spring Cloud Contract 和 SpringBoot 以及 Junit 的集成更简单方便。而且 Spring Cloud Contract 的另一个优势是它可以自动生成服务方的自动化测试。


 全文完


以下文章您可能也会感兴趣:

我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。


杏仁技术站

长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。




<think> 嗯,用户这次想了解基于微服务的软件组装式应用开发,需要结合之前提供的三篇引用内容来组织回答。用户是技术负责人或架构师角色,关注的是方法论和落地实践,而非基础概念。 从引用材料看,关键信息点很明确:引用[1]强调微服务架构实施过程(分层设计→服务细化→容器化部署),引用[2]指出组装式应用是Gartner认证的技术趋势,引用[3]则提供Golang依赖注入的技术细节。需要把这三者有机融合,重点突出“组装”这个核心特征。 用户上次的问题关于快速排序,但当前问题完全转向架构设计领域,看来是新的技术需求。考虑到用户身份,回复应该保持专业深度,避免基础概念解释。特别要注意引用[3]提到的“代码解耦”“可测试性”等痛点,这可能是用户没明说但实际关心的。 技术路线可以这样展开:先阐明组装式开发与微服务的关系(引用[2]的趋势背景),再分阶段说明实施方法(呼应引用[1]的项目生命周期),最后用依赖注入等技术案例(引用[3])佐证可组装性。部署环节必须强调容器化的必要性——这是实现弹性组装的基础设施保障。 需要警惕两个易错点:1)不能简单堆砌引用内容,要把服务拆分、组件化、容器部署等环节串联成完整工作流;2)避免过度聚焦Golang细节,虽然引用[3]以此为例,但用户问的是通用方法论。工具推荐部分可以提Spring Cloud这类更主流的方案作为补充。 最后相关问题设计要引导深入探讨:趋势分析类问题满足决策者视角,技术选型类问题切合工程师需求,再通过组织变革问题覆盖管理维度,形成立体维度覆盖。 </think> 基于微服务的软件组装式应用开发是一种通过组合独立部署的细粒度服务来构建系统的架构方法。结合引用内容和行业实践,其核心要素如下: --- ### **一、核心概念** 1. **微服务架构** - 将单体应用拆分为**独立部署、松耦合**的小型服务(引用[1]) - 每个服务聚焦单一业务能力(如用户管理、支付服务) - 通过API通信,支持不同技术栈实现 2. **组装式应用(Composable Application)** - 引用[2]指出:通过**模块化组件**快速组合新应用 - 核心特征:**可复用性、敏捷组装、业务适应性** - 典型场景:复用认证/日志/消息等公共服务模块 --- ### **、关键技术方法** | **阶段** | **关键实践** | **技术支撑** | |----------------|-----------------------------------------------------------------------------|------------------------------------------| | **服务设计** | 1. 按业务域拆分服务边界(引用[1])<br>2. 定义标准化API契约 | DDD(领域驱动设计)<br>OpenAPI规范 | | **开发组装** | 1. 依赖注入解耦(引用[3])<br>2. 组件库管理复用模块 | Golang/Wire(引用[3])<br>Spring Cloud | | **部署运维** | 1. 容器化封装服务(引用[1])<br>2. 服务网格治理通信 | Docker/Kubernetes<br>Istio/Linkerd | | **持续交付** | 1. 独立服务流水线<br>2. 自动化版本编排 | GitOps<br>Argo CD | --- ### **三、核心实践案例** 1. **服务分层组装**(引用[1]) - **基础服务层**:数据库/消息队列等基础设施服务 - **业务能力层**:订单处理、库存管理等核心业务微服务 - **聚合层**:通过API网关组合多个服务提供统一接口 2. **依赖注入实现解耦**(引用[3]) ```go // Golang依赖注入示例(使用Wire框架) func InitOrderService() *OrderService { wire.Build( NewDBConnection, // 数据库依赖 NewPaymentClient, // 支付服务客户端 NewOrderService, // 组装订单服务 ) return &OrderService{} } ``` - **优势**:更换数据库或支付服务时无需修改业务代码 3. **容器化部署流程**(引用[1]) ```mermaid graph LR A[微服务代码] --> B[Docker镜像构建] B --> C[Kubernetes部署] C --> D[服务自动扩缩容] ``` --- ### **四、核心优势与挑战** | **优势** | **挑战** | |------------------------------|------------------------------| | 快速响应业务变化(引用[2]) | 分布式事务一致性 | | 故障隔离提升系统韧性 | 跨服务调试复杂度高 | | 技术栈灵活性 | 运维监控复杂度提升 | | 组件复用降低开发成本 | 服务网格学习曲线陡峭 | --- ### **五、发展趋势** 1. **Serverless集成**:将微服务与FaaS(函数即服务)结合,实现更细粒度组装(引用[2]演进方向) 2. **AI辅助设计**:利用AI生成服务接口与编排逻辑 3. **混沌工程标配化**:通过故障注入验证组装系统的健壮性 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值