使用Permutations引爆你的JUnit5测试

翻译:吴嘉俊,叩丁狼高级讲师。

写JUnit测试是一个非常枯燥无聊的事情。本文介绍使用permutations配合TestFactory方法和DynamicTest对象,让你的测试事半功倍。

在本文中,我将使用Speedment【注:Speedment是一款ORM工具,使用Lambda表达式来简化SQL的书写】,因为它已经包含了一个完善的Permutation类供我们立刻使用。Speedment支持使用Java标准流(Java streams)的方式来处理数据表。Speedment是一款开源的工具,支持多个社区免费版本数据库。

测试一个Stream

考虑下面这个Junit5测试用例:

@Test
void test() {

    List<String> actual = Stream.of("CCC", "A", "BB", "BB")
        .filter(string -> string.length() > 1)
        .sorted()
        .distinct()
        .collect(Collectors.toList());

    List<String> 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<DynamicTest> 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<Stream<String>> FILTER_OP = new UnaryOperator<Stream<String>>() {
    @Override
    public Stream<String> apply(Stream<String> s) {
        return s.filter(string -> string.length() > 1);
    }

    @Override
    public String toString() {
        return "filter";
    }
 };


UnaryOperator<Stream<String>> DISTINCT_OP = new UnaryOperator<Stream<String>>() {
    @Override
    public Stream<String> apply(Stream<String> s) {
        return s.distinct();
    }

    @Override
    public String toString() {
        return "distinct";
    }
};

UnaryOperator<Stream<String>> SORTED_OP = new UnaryOperator<Stream<String>>() {
    @Override
    public Stream<String> apply(Stream<String> 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<DynamicTest> testAllPermutations() {

    List<String> 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<String> 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

 

猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/83054197