首页 > 技术文章 > 技术文章精译 >

使用Permutations引爆你的JUnit5测试

更新时间:2018-10-14 | 阅读量(912)

写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
叩丁狼学员采访 叩丁狼学员采访
叩丁狼头条 叩丁狼头条
叩丁狼在线课程 叩丁狼在线课程