JUnit 5 FormattedSource源码(https://github.com/mikemybytes/junit5-formatted-source)允许按照用户定义的格式,以可读的方式定义测试用例参数。因此,它可以用来提高测试的可读性。让我们看一个例子:
class CalculatorTest {
private final Calculator calculator = new Calculator();
@FormattedSourceTest(format = "{0} + {1} = {2}", lines = {
"1 + 2 = 3",
"3 + 4 = 7"
})
void calculatesSum(int a, int b, int expectedSum) {
Assertions.assertEquals(expectedSum, calculator.sum(a, b));
}
}
测试执行输出显示了使用FormattedSource的另一个好处-该库为我们定制显示的测试用例名称,而无需付出任何额外的努力:
calculatesSum(int, int, int) ✔
├─ 1 + 2 = 3 ✔
└─ 3 + 4 = 7 ✔
当然,它可以做的远不止这些
这篇文章不会详尽地解释FormattedSource的可能。这就是《用户指南》的初衷。相反,我想展示Lib可以解决什么问题,以及某些东西是如何在引擎下工作的。
为什么?
我想到的@CsvSource
最令人惊讶的用法可能是使用多字符字符串作为字段分隔符。尽管从CSV格式的角度来看,这是值得怀疑的,但它的效果正如预期的那样:
@ParameterizedTest
@CsvSource(delimiterString = "maps to", textBlock = """
'foo' maps to 'bar'
'junit' maps to 'jupiter'
""")
void shouldMapPair(String input, String expected) {
String actual = pairMapper.map(input);
Assertions.assertEquals(expected, actual);
}
我发现这种定义测试用例的方法非常强大。如果它一次支持多个分隔符String,我们可以将其用于更复杂的格式,如:
<input> maps to <output> using rule <ruleId>
使用@CsvSource
定义这样的测试从一开始就感觉有点麻烦。我没有进一步滥用它,而是决定创建一个替代方案。FormattedSource就是这样诞生的。
FormattedSource库允许以简单灵活的方式定义测试用例。通过指定格式字符串,我们定义了所有测试用例定义的外观。再加上一种相当轻松的处理额外空白的方法,这使我们能够专注于可读性。使用@CsvSource
无法实现的目标已成为标准解决方案:
@FormattedSourceTest(format = "{0} maps to {1} using rule {2}", textBlock = """
'foo' maps to 'bar' using rule 486
'junit' maps to 'jupiter' using rule 44
""")
void mapsOneValueToAnother(String input, String mapped, int ruleId) {
// ...
}
除了测试用例参数占位符之外,格式字符串没有特殊意义。换句话说,您每次都可以用最易读的方式定义它。您也可以定义自己的argumentPlaceholder
,而不是通过它们的索引来引用参数(就像内置的@DisplayName
注释一样):
@FormattedSourceTest(
format = "replaces ? with ?", argumentPlaceholder = "?",
textBlock = """
replaces foo with bar
""")
void replacesOneStringWithAnother(String toBeReplaced, String replacement) {
// toBeReplaced == "foo", replacement == "bar"
}
两个注解
对于以前使用JUnit5参数化测试的人来说,使用一个@FormattedSourceTest
注释可能会显得有些奇怪。@ParameterizedTest
在哪里?它是如何操作显示的测试用例名称的?
这里没有魔法。我们的第一个例子也可以这样实现:
class CalculatorTest {
private final Calculator calculator = new Calculator();
@ParameterizedTest(name = "{0} + {1} = {2}")
@FormattedSource(format = "{0} + {1} = {2}", lines = {
"1 + 2 = 3",
"3 + 4 = 7"
})
void calculatesSum(int a, int b, int expectedSum) {
Assertions.assertEquals(expectedSum, calculator.sum(a, b));
}
}
@FormattedSource
它只是一个自定义的参数化测试参数源,您可以一直这样使用它。事实上,@FormattedSourceTest
和@FormattedSource
注释共享相同的参数集,因此它们可以互换使用。
将两个相当标准的注释合并为一个注释的好处是它的表达性。因为格式字符串总是代表一个人类可读的字符串,所以它感觉就像是显示的测试用例名称的合适候选者。使用@FormattedSourceTest
可以避免重复@ParameterizedTest#名称中的格式字符串。
这两个声明都支持内置@CsvSource
的许多功能,比如使用Java 15+文本块(而不是行数组)声明测试用例,以及定义自己的null
值。
什么时候
当以人类可读的方式表达测试用例时,使用FormattedSource可能是一个好主意。当参数的数量合理并且可以很容易地表示为相对较短的字符串时,您可以将其视为@CsvSource
的竞争对手。
FormattedSource可以很好地测试映射器和编码器等映射代码。然后,我们可以在格式字符串中使用一个简单的箭头:
@FormattedSourceTest(
format = "{0} -> {1}",
textBlock = """
'foo' -> 'bar'
'junit' -> 'jupiter'
"""
)
void mapsOneValueToAnother(String input, String expectedValue) { ... }
或者将其替换为更具描述性的字符串,如{0}
映射到{1}
。或者,我们可以让整个测试用例定义听起来像一个真实的句子:
@FormattedSourceTest(
format = "encodes {0} seconds as {1}",
lines = {
"encodes 15 seconds as 'PT15S'",
"encodes 180 seconds as 'PT3M'",
"encodes 172800 seconds as 'PT48H'"
}
)
void encodesDurationAsIso8601(long seconds, String expected) { ... }
当然,我们不仅仅局限于映射代码和传递字符串作为参数。FormattedSource只是一个JUnit 5参数源,因此它可以从内置的参数转换中受益。这允许应用更具创造性的方法-例如,测试我们的HTTP API:
enum UserType { CUSTOMER, ADMIN }
@FormattedSourceTest(
format = "{0} returns {1} for {2}",
textBlock = """
/api/items returns 200 for CUSTOMER
/api/items returns 404 for ADMIN
/api/admin/inventory returns 401 for CUSTOMER
/api/admin/inventory returns 200 for ADMIN
"""
)
void protectsEndpointsBasedOnUserType(URI uri, int httpCode, UserType type) {
User user = TestUserFactory.authenticated(type);
testHttpClient.get(uri).returnsResponseCode(httpCode);
}
然而,这并不意味着你应该忘记其他的论据来源!当自变量或测试用例的数量很高时,引入可读格式可能不切实际。在这些情况下,@CsvSource
和鲜为人知的@CsvFileSource
注释可能是更好的选择。此外,当参数不能很容易地表示为字符串时,FormattedSource也没有帮助。
该代码已被组织成一个多模块Maven项目。如果没有Java平台模块系统(JPMS)的支持,这一点就不值得在这里提及。该库有两个子模块:
junit5-formatted-source-parent
+- junit5-formatted-source
| +- src (library code)
| \- test (unit, classpath tests)
\- junit5-formatted-source-tests
\- test ("integration", module path tests)
junit5格式的源测试包括所有验证注释行为的“集成”测试。这个Maven子模块有自己的module-info.java
文件,显式导入com.mikemybytes.junit5.formated
作为依赖项。因此,它允许我测试JPMS支持以及功能。它还作为一个在模块路径上运行时如何使用库的实例。
1.0.0版本仍然需要至少Java 11才能运行。尽管我宣传至少在最新的LTS版本上运行,但我也希望避免为后期采用者关闭大门。此外,坚持使用与Spring Boot 3.0和Quarkus 3.0相同的基线版本感觉是一个合理的想法。同时,junit5格式的源代码测试模块中需要Java17+,因此可以在那里测试文本块等较新的语言功能。
总结
FormattedSource库为使用JUnit5编写参数化测试带来了另一种风格。这是一种以人类可读的方式定义测试用例的简单但功能强大的方法。
最近的1.0.0版本旨在强调API的稳定性和整体项目的成熟度。然而,提供所有最初设想的功能并不能阻止进一步的增强。
原文链接:https://mikemybytes.com/2023/05/03/introducing-junit5-formattedsource/
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2916.html
暂无评论