动态测试

重要性:★★☆☆☆

注解为@Test的测试方法是静态的,因为其行为是在编译时完全决定的,不能在运行时进行修改。Assumptions(假设)提供了一定程度的动态行为(根据运行时环境条件动态调整测试执行)。

JUnit Jupiter提供了一种新的测试编程模型。可以通过注解了@TestFactory的测试工厂方法在运行时生成动态测试。

技术上而言,@TestFactory测试工厂方法必须返回单个的DynamicNode实例,或由DynamicNode组成的Stream,Collection,Iterable,Iterator或数组。DynamicNode有两个可实例化的子类:DynamicContainerDynamicTestDynamicContainer包括一个显示名和一组动态的子节点,能够用来创建任意多层的嵌套动态节点树。DynamicTest实例将被延迟执行,能够动态地甚至非确定性地生成测试用例。

@TestFactory返回的任何流将由JUnit通过调用Stream.close()正常关闭,因此使用类似Files.lines()这样的资源是安全的。

@Test方法一样,@TestFactory方法也必须是非private、非静态的,可以包含能够通过参数解析器解析的参数。

DynamicTest是由测试工厂方法在运行时生成的测试用例,它由一个显示名和一个Executable组成。

@Test不同,生命周期方法是针对整个@TestFactory测试工厂方法的,而不是针对测试工厂方法生成的每个测试用例。也就是说,模板方法生成的多个测试用例共享相同的测试类实例,一个测试用例修改了测试类的状态可能会影响下一个测试用例,因此必须小心对待。

下面是动态测试示例。

package yang.yu.tdd.dynamic;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;
import yang.yu.tdd.Calculator;

import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static yang.yu.tdd.StringUtils.isPalindrome;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

class DynamicTestsDemo {

    private final Calculator calculator = new Calculator();

    // This will result in a JUnitException!
    @TestFactory
    List<String> dynamicTestsWithInvalidReturnType() {
        return Arrays.asList("Hello");
    }

    @TestFactory
    Collection<DynamicTest> dynamicTestsFromCollection() {
        return Arrays.asList(
                dynamicTest("1st dynamic test", () -> assertThat(isPalindrome("madam")).isTrue()),
                dynamicTest("2nd dynamic test", () -> assertThat(calculator.multiply(2, 2)).isEqualTo(4))
        );
    }

    @TestFactory
    Iterable<DynamicTest> dynamicTestsFromIterable() {
        return Arrays.asList(
                dynamicTest("3rd dynamic test", () -> assertThat(isPalindrome("madam")).isTrue()),
                dynamicTest("4th dynamic test", () -> assertThat(calculator.multiply(2, 2)).isEqualTo(4))
        );
    }

    @TestFactory
    Iterator<DynamicTest> dynamicTestsFromIterator() {
        return Arrays.asList(
                dynamicTest("5th dynamic test", () -> assertThat(isPalindrome("madam")).isTrue()),
                dynamicTest("6th dynamic test", () -> assertThat(calculator.multiply(2, 2)).isEqualTo(4))
        ).iterator();
    }

    @TestFactory
    DynamicTest[] dynamicTestsFromArray() {
        return new DynamicTest[] {
                dynamicTest("7th dynamic test", () -> assertThat(isPalindrome("madam")).isTrue()),
                dynamicTest("8th dynamic test", () -> assertThat(calculator.multiply(2, 2)).isEqualTo(4))
        };
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStream() {
        return Stream.of("racecar", "radar", "mom", "dad")
                .map(text -> dynamicTest(text, () -> assertThat(isPalindrome(text)).isTrue()));
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromIntStream() {
        // Generates tests for the first 10 even integers.
        return IntStream.iterate(0, n -> n + 2).limit(10)
                .mapToObj(n -> dynamicTest("test" + n, () -> assertThat(n % 2).isEqualTo(0)));
    }

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTests() {

        // Generates random positive integers between 0 and 100 until
        // a number evenly divisible by 7 is encountered.
        Iterator<Integer> inputGenerator = new Iterator<Integer>() {

            Random random = new Random();
            int current;

            @Override
            public boolean hasNext() {
                current = random.nextInt(100);
                return current % 7 != 0;
            }

            @Override
            public Integer next() {
                return current;
            }
        };

        // Generates display names like: input:5, input:37, input:85, etc.
        Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;

        // Executes tests based on the current input value.
        ThrowingConsumer<Integer> testExecutor = (input) -> assertThat(input % 7).isNotEqualTo(0);

        // Returns a stream of dynamic tests.
        return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
    }

    @TestFactory
    Stream<DynamicNode> dynamicTestsWithContainers() {
        return Stream.of("A", "B", "C")
                .map(input -> dynamicContainer("Container " + input, Stream.of(
                        dynamicTest("not null", () -> assertThat(input).isNotNull()),
                        dynamicContainer("properties", Stream.of(
                                dynamicTest("length > 0", () -> assertThat(input.length()).isPositive()),
                                dynamicTest("not empty", () -> assertThat(input).isNotEmpty())
                        ))
                )));
    }

    @TestFactory
    DynamicNode dynamicNodeSingleTest() {
        return dynamicTest("'pop' is a palindrome", () -> assertThat(isPalindrome("pop")).isTrue());
    }

    @TestFactory
    DynamicNode dynamicNodeSingleContainer() {
        return dynamicContainer("palindromes",
                Stream.of("racecar", "radar", "mom", "dad")
                        .map(text -> dynamicTest(text, () -> assertThat(isPalindrome(text)).isTrue())
                        ));
    }

}

results matching ""

    No results matching ""