翻译:吴嘉俊,叩丁狼高级讲师。
写JUnit测试是一个非常枯燥无聊的事情。本文介绍使用permutations配合TestFactory方法和DynamicTest对象,让你的测试事半功倍。
在本文中,我将使用Speedment【注:Speedment是一款ORM工具,使用Lambda表达式来简化SQL的书写】,因为它已经包含了一个完善的Permutation类供我们立刻使用。Speedment支持使用Java标准流(Java streams)的方式来处理数据表。Speedment是一款开源的工具,支持多个社区免费版本数据库。
测试一个Stream
考虑下面这个Junit5测试用例:
@Test
void test() {
List actual = Stream.of("CCC", "A", "BB", "BB")
.filter(string -> string.length() > 1)
.sorted()
.distinct()
.collect(Collectors.toList());
List expected = Arrays.asList("BB", "CCC");
assertEquals(actual, expected);
}
可以看到,在测试中我们创建了一个Stream,包含了’CCC’,’AA’,’BB’,’BB’这四个元素,然后通过第一个过滤方法去掉了”A”元素(因为length小于1),紧接着,对元素进行了排序,得到元素序列为”BB”,”BB”,”CCC”,接下来,一个distinct操作,去掉所有的重复元素,留下序列”BB”,”CCC”,最后通过一个collect方法搜集到一个List中。
简单的思考这段代码,我们不难发现,对于stream的操作:filter(),sorted(),distinct(),他们三个方法的执行顺序,和最终的结果应该是没有关系的。换句话说,这三个方法的执行顺序的改变应该得到相同的结果。
但是我们知道,要完成这个测试,考虑到排列组合的问题,我们需要手动使用JUnit完成额外的6个测试,这是非常无聊的一件事情。
使用TestFactory
首先,代替书写独立的测试,我们可以使用JUnit5提供的TestFactory来提供一组DynamicTest对象。下面是一段简单的验证代码:
@TestFactory
Stream testDynamicTestStream() {
return Stream.of(
DynamicTest.dynamicTest("A", () -> assertEquals("A", "A")),
DynamicTest.dynamicTest("B", () -> assertEquals("B", "B"))
);
}
这段代码会创建两个测试用例,一个命名为A,一个命名为B。注意我们可以直接返回一组DynamicTest对象的Stream,不需要搜集到一个集合里面。
使用Permutation(组合)
Permutation类可以为我们自动的创建出任意类型的元素的可能组合。下面是一个组合String类型的例子:
Permutation.of("A", "B", "C")
.map(
is -> is.collect(Collectors.toList())
)
.forEach(System.out::println);
因为Permutation类的返回值是一个元素类型为Stream的Stream,所以我们首先使用map方法,把每一个Stream元素collect为一个list,然后再输出,得到的结果为:
[A, B, C]
[A, C, B]
[B, A, C]
[B, C, A]
[C, A, B]
[C, B, A]
可以清楚的看到,我们得到了由元素”A”,”B”,”C”进行排列组合的所有可能结果。
自定义操作类
在本文中,我选择通过创建一个中间操作类来代替直接使用lambda表达式中的操作,因为我可以再中间类中覆写toString方法。在正常情况下,我还是建议我们直接使用lambda表达式操作:
UnaryOperator> FILTER_OP = new UnaryOperator>() {
@Override
public Stream apply(Stream s) {
return s.filter(string -> string.length() > 1);
}
@Override
public String toString() {
return "filter";
}
};
UnaryOperator> DISTINCT_OP = new UnaryOperator>() {
@Override
public Stream apply(Stream s) {
return s.distinct();
}
@Override
public String toString() {
return "distinct";
}
};
UnaryOperator> SORTED_OP = new UnaryOperator>() {
@Override
public Stream apply(Stream s) {
return s.sorted();
}
@Override
public String toString() {
return "sorted";
}
};
测试组合
接下来我们就可以很方便的测试我们的操作的组合了:
void printAllPermutations() {
Permutation.of(
FILTER_OP,
DISTINCT_OP,
SORTED_OP
)
.map(
is -> is.collect(toList())
)
.forEach(System.out::println);
}
会得到如下的输出:
[filter, distinct, sorted]
[filter, sorted, distinct]
[distinct, filter, sorted]
[distinct, sorted, filter]
[sorted, filter, distinct]
[sorted, distinct, filter]
到此,我们可以看到,我们想执行的操作的组合已经正确生成。
组合到一起
把上面学到的内容组合起来,我们就可以创建一个用于测试所有操作组合起来的可能情况了:
@TestFactory
Stream testAllPermutations() {
List expected = Arrays.asList("BB", "CCC");
return Permutation.of(
FILTER_OP,
DISTINCT_OP,
SORTED_OP
)
.map(is -> is.collect(toList()))
.map(l -> DynamicTest.dynamicTest(
l.toString(),
() -> {
List actual = l.stream()
.reduce(
Stream.of("CCC", "A", "BB", "BB"),
(s, oper) -> oper.apply(s),
(a, b) -> a
).collect(toList());
assertEquals(expected, actual);
}
)
);
}
注意我们是如何使用Stream::reduce方法把我们定义的操作施加在Stream.of(“CCC”, “A”, “BB”, “BB”)上的。后面的(a,b)->a是没有什么作用的,仅仅只是占位使用。
警告
值得注意的一点就是,这种测试方案能适应的作用范围。组合是一个较为复杂的操作,时间复杂度为O(n!),当数据量较大的时候,产生的结果集是很大的,比如8个元素的组合有40320个可能,而9个元素的组合瞬间达到362880种。
这是一把双刃剑,我们能够低成本的得到非常多的测试用例,但是执行这些测试用例的时间,是我们需要承受的。
小结
在JUnit5里面,灵活的使用Permutation,DynamicTest和TestFactory是能够极大的帮助我们创建测试。注意一点的就是不要在过多的元素之上使用组合,你的测试会真的”引爆“的。
原文:https://www.javacodegeeks.com/2018/10/blow-junit5-tests-permutations.html
想获取更多技术视频,请前往叩丁狼官网:http://www.wolfcode.cn/openClassWeb_listDetail.html