前言
本文将介绍如何利用代码的自动生成,对微服务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]+\"}");
模板
对于请求和响应数据,我们希望以下列三种方式定义:
- 将数据定义在模板文件中,在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测试工具。后续文章将继续介绍工具的实现。
码字不易,如果对你有帮助,请点赞关注加分享,感谢!