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

栏目: Java · 发布时间: 7年前

内容简介:微服务环境下的集成测试探索(二):契约式测试

作者 | 章烨明

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

杏仁医生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 。

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

杏仁技术站

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art and Science of Java

The Art and Science of Java

Eric Roberts / Addison-Wesley / 2007-3-1 / USD 121.60

In The Art and Science of Java, Stanford professor and well-known leader in CS Education Eric Roberts emphasizes the student-friendly exposition that led to the success of The Art and Science of C. By......一起来看看 《The Art and Science of Java》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具