就这么神奇!自动生成的微服务API测试:设计篇

1,440 阅读6分钟

前言

本文将介绍如何利用代码的自动生成,对微服务API进行快速高效的测试,全文分成三部分:需求篇设计篇实现篇。需求篇将介绍项目背景,微服务API测试中的痛点,以及我们理想的解决方案。设计篇将介绍如何针对需求,设计可行的实现方案。实现篇将从代码着手,介绍具体的实现。小伙伴们可以自行点开感兴趣的部分。

生成测试代码

为了可以实现 No Code 测试,不可避免的,我们需要用Java代码来生成另外一部分Java代码。应对这个需求,我们采用了 JavaPoet

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

测试代码框架

我们的基本思想是,将API测试所需的输入定义到配置文件中,然后在测试代码中引用配置文件,作为测试的参数。采用JUnit5的参数化测试框架,可以帮助我们轻松实现这个想法。

参数化测试可以使用不同的参数多次运行测试。 它们的声明就像常规的 @Test 方法一样,但使用 @ParameterizedTest 注解代替。 此外,必须至少声明一个参数源,该参数源将为每次调用提供输入参数,然后在测试方法中使用这些参数。

以下示例演示了一个参数化测试,该测试使用 @ValueSource 注解将 String 数组指定为参数源。

@ParameterizedTest 
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) 
void palindromes(String candidate) { 
    assertTrue(StringUtils.isPalindrome(candidate)); 
}

数据库

预置测试数据

我们需要使用CSV文件或者SQL文件,将预置数据保存到数据库。SpringTest DBUnit是一个很好的选择,Spring DBUnit 提供了 Spring 测试框架和流行的 DBUnit 项目之间的集成。 它允许我们使用简单的注释设置和清除数据库表,并在测试完成后检查预期的数据。

使用它提供的@DatabaseSetup注解功能,可以很方便的设置预置数据。并且支持SQL、CSV、Xml等多种格式。

检查结果数据

另外,我们需要将测试后的数据库快照跟CSV文件中的期待数据进行对比,判断是否通过测试。利用SpringTest DBUnit提供的@ExpectedDatabase注解功能,可以比对测试后的数据库快照。

HTTP访问

处理HTTP请求

我们决定采用REST Assured来处理HTTP请求和响应。REST Assured 是一种 Java DSL,用于简化构建在 HTTP Builder 之上的 REST 服务测试。 它支持 POST、GET、PUT、DELETE、OPTIONS、PATCH 和 HEAD 请求,并且可用于验证这些请求的响应。

使用 Java 测试和验证 REST 服务比使用 Ruby 和 Groovy 等动态语言更难。 REST Assured 将这些语言的简单性带入了 Java 领域。 例如,如果您的 HTTP 服务器在“http://localhost:8080/lotto/{id}”处返回以下 JSON:

 {
    "lotto":{
       "lottoId":5,
       "winning-numbers":[2,45,34,23,7,5,3],
       "winners":[
          {
             "winnerId":23,
             "numbers":[2,45,34,23,3,5]
          },
          {
             "winnerId":54,
             "numbers":[52,3,12,11,18,22]
          }
       ]
    }
 }

你可以简单的使用REST Assured来验证响应中你感兴趣的部分:

 @Test public void
 lotto_resource_returns_200_with_expected_id_and_winners() {
 
     when().
             get("/lotto/{id}", 5).
     then().
             statusCode(200).
             body("lotto.lottoId", equalTo(5),
                  "lotto.winners.winnerId", hasItems(23, 54));
 
 }

检查HTTP响应

对于JSON格式的响应数据,我们希望在校验时,能够支持基于格式的校验,比如检查JSON中特定的字段格式是否为数字,字符串等,而忽略字段的具体内容。

基于上述需求,我们决定采用 JsonUnit 。JsonUnit 是一个用于在测试中简化 JSON 校验的库。比如我们想检查字段 c 是否为数字时,可以这样写:

 // Type placeholders
 assertThatJson("{\"a\":1, \"b\": {\"c\" :3}}")
     .isObject().containsValue(json("{\"c\" :\"${json-unit.any-number}\"}"));

另外,它还支持如下基于正则表达式的校验。

 // Regular expressions
 assertThatJson("{\"test\": \"ABCD\"}")
     .isEqualTo("{\"test\": \"${json-unit.regex}[A-Z]+\"}");

模板

对于请求和响应数据,我们希望以下列三种方式定义:

  1. 将数据定义在模板文件中,在Yaml中配置模板的路径和参数

基于上述需求,我们决定采用 Mustache 作为模板引擎。Mustache是一个无逻辑(Logic-less)的模板引擎,非常简单易用。OpenAPI Generator也是采用了该技术作为代码生成的模板。模板示例如下:

 Hello {{name}}
 You have just won {{value}} dollars!
 {{#in_ca}}
 Well, {{taxed_value}} dollars, after taxes.
 {{/in_ca}}

输入参数如下:

 {
   "name": "Chris",
   "value": 10000,
   "taxed_value": 10000 - (10000 * 0.4),
   "in_ca": true
 }

输出结果:

 Hello Chris
 You have just won 10000 dollars!
 Well, 6000.0 dollars, after taxes.

值得注意的是,Mustache采用 {{...}} 符号来标识参数,跟JsonUnit使用的 $ 胡不冲突。

作为Task在Gradle中执行

我们希望可以在本地环境和CI环境都可以通过gradle test命令生成测试代码,并执行测试

为了实现上述需求,我们需要开发一个自定义的 Gradle 插件。在这个插件中调用我基于Java编写的测试代码生成器,生成API测试代码,同时配置 test 任务的依赖关系,在每次执行 test 任务时,触发自定义的插件。

功能汇总

我们的测试代码自动生成工具的功能汇总如下:

  • 自动生成API的测试Class

    • 根据OAS文件的定义,采用JavaPoet,以Controller Class为单位,自动生成测试Class。
    • 以API的端点(Endpoint)为单位,在测试Class中自动生成测试方法。
  • 配置测试用例

    • 在测试用例规格文件(YAML)中记载测试方法对应的参数。
    • 可以在同一个测试用例规格文件中定义多个测试用例的参数。
    • 使用ParameterizedTest调用测试用例规格文件作为参数输入。
  • 准备测试数据

    • 使用DBUnit将CSV或者SQL文件的测试数据保存到数据库中。
    • 在测试用例规格文件(YAML)或者单独的JSON文件中设定HTTP(路径参数、Header、查询参数、请求主体)的数据。
  • 校验期望数据

    • 使用DBUnit校验在CSV文件中设定的数据库期望值数据。
    • 校验在测试用例规格文件(YAML)或者单独的JSON文件中设定的HTTP(Header、状态、响应主体)的期望值数据。
  • 执行测试

    • 使用自定义Gradle插件生成测试代码并执行测试

总结

本篇介绍了针对前文所述的各项需求,如何利用现有的各种 OSS 设计我们API测试工具。后续文章将继续介绍工具的实现。

码字不易,如果对你有帮助,请点赞关注加分享,感谢!

相关文章

参考链接