动态传入表名 注解_JUnit 5动态测试

0b27c2c631a46d239e662960c0a7f032.png

动态测试

@Test注解标注的方法被认为是静态的,在编译时期它们就被确定了。而动态测试允许我们在代码运行时期产生测试用例。

JUnit5中引入了“动态测试(Dynamic tests)”的概念,在动态测试中,允许我们通过工厂方法的方式批量生产测试用例,而这些产生的测试用例会被框架自动去执行,测试用例是在运行时动态产生的,而不是在编译时期,因而动态测试实例会被延迟执行。

@TestFactory

@TestFactory是动态测试的核心注解,它用于标志一个方法为测试用例的工厂方法,而不是普通的测试方法,@TestFactory方法本身不是测试用例,而是测试用例的工厂,它用来生产测试方法。如果没有理解也不要紧,看到实例或许更容易懂。

这个方法不能被privatestatic修饰,并且它必须有返回值,这也是它区别于普通测试方法的一个重要标志。

对于方法的返回值也是有要求的,它必须返回:StreamCollectionIterableIterator或是数组形式的DynamicNode类型,对于方法返回的Stream流(例如:Files.lines(),Files.list()等)会被自动关闭,因而你无需手动关闭。

DynamicNode

DynamicNode是个抽象类,有两个成员属性:

public abstract class DynamicNode {
    // 生成的测试方法在测试视图展示的名称
    private final String displayName;
    // 测试方法可能用到的资源URI,可以是null
    private final URI testSourceUri;

    DynamicNode(String displayName, URI testSourceUri) {
        this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank");
        this.testSourceUri = testSourceUri;
    }

    public String getDisplayName() {
        return this.displayName;
    }

    public Optional<URI> getTestSourceUri() {
        return Optional.ofNullable(testSourceUri);
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append("displayName", displayName)
                .append("testSourceUri", testSourceUri).toString();
    }
}

这个抽象类有两个直接具体子类:DynamicTestDynamicContainer

DynamicTest

public class DynamicTest extends DynamicNode {
    // 构造一个DynamicTest实例,可以把它认为就是一个测试用例了,displayName指定这个测试用例
    // 的名称(会在测试视图节点树中显示),executable稍后讲解
    public static DynamicTest dynamicTest(String displayName, Executable executable) {
        return new DynamicTest(displayName, null, executable);
    }
    // 如果有额外需要的测试资源通过testSourceUri传入,java.net.URI
    public static DynamicTest dynamicTest(String displayName, URI testSourceUri, Executable executable) {
        return new DynamicTest(displayName, testSourceUri, executable);
    }
    // 将传入的inputGenerator构造成Stream流
    public static <T> Stream<DynamicTest> stream(Iterator<T> inputGenerator,
            Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) {

        Preconditions.notNull(inputGenerator, "inputGenerator must not be null");
        Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null");
        Preconditions.notNull(testExecutor, "testExecutor must not be null");
        // 将Iterator类型的inputGenerator通过java.util.Spliterators#spliteratorUnknownSize转换为Stream
        return StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false)
            // 调用自身的静态方法dynamicTest(String displayName, Executable executable)
                .map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input)));
    }

    // 产生的测试用例的具体执行逻辑
    private final Executable executable;

    private DynamicTest(String displayName, URI testSourceUri, Executable executable) {
        super(displayName, testSourceUri);
        this.executable = Preconditions.notNull(executable, "executable must not be null");
    }

    public Executable getExecutable() { return this.executable; }
}

再看Executable类型,这是生成的测试用例具体的执行逻辑,可把它当成普通测试方法的方法体部分,它是函数式接口,因而支持lambda表达式:

@FunctionalInterface
public interface Executable {
    void execute() throws Throwable;
}

org.junit.jupiter.api.function.ThrowingConsumer也是函数式接口,类似于Java 8中的Consumer,只不过它允许抛出异常:

@FunctionalInterface
public interface ThrowingConsumer<T> {
    void accept(T t) throws Throwable;
}

看个简单示例:

@TestFactory
    Stream<DynamicTest> dynamicTest() {
        return IntStream.rangeClosed(1, 10)
                .mapToObj(num -> DynamicTest.dynamicTest(
                        "exec - " + num, () -> System.out.println("任务: " + num)));
    }

上面动态创建了10个测试用例,Executable我使用了lambda简化,方法返回的Stream<DynamicTest>会被框架自动获取并执行(调用Executable接口方法),这已经不是我们需要关心的事情:

a0ae3b7793633a2d438368a30714e67f.png

DynamicContainer

讲完了DynamicTest,再看DynamicContainer(前面讲过的部分不再重提):

public class DynamicContainer extends DynamicNode {
    // 注意这里接收一个DynamicNode类型的Iterable,前面讲过DynamicNode有两个直接具体子类:
    // DynamicTest和DynamicContainer,除了放DynamicTest还可放DynamicContainer,
    // 这样可做到无限嵌套
    public static DynamicContainer dynamicContainer(String displayName, Iterable<? extends DynamicNode> dynamicNodes) {
        return dynamicContainer(displayName, null, StreamSupport.stream(dynamicNodes.spliterator(), false));
    }

    public static DynamicContainer dynamicContainer(String displayName, Stream<? extends DynamicNode> dynamicNodes) {
        return dynamicContainer(displayName, null, dynamicNodes);
    }

    public static DynamicContainer dynamicContainer(String displayName, URI testSourceUri,
            Stream<? extends DynamicNode> dynamicNodes) {

        return new DynamicContainer(displayName, testSourceUri, dynamicNodes);
    }
    // 略
    private final Stream<? extends DynamicNode> children;

    private DynamicContainer(String displayName, URI testSourceUri, Stream<? extends DynamicNode> children) {
        super(displayName, testSourceUri);
        Preconditions.notNull(children, "children must not be null");
        this.children = children;
    }

    public Stream<? extends DynamicNode> getChildren() { return children;
    }
}

直接通过示例感受下:

@TestFactory
    Stream<DynamicContainer> dynamicContainerStream() {
        return IntStream.rangeClosed(1, 5)
                .mapToObj(num -> {
                    // 将Executable接口实现提取出来,这样不用写两遍
                    Executable executable = () -> System.out.println(num);
                    // 测试用例
                    Stream<DynamicTest> dynamicTestStream = Arrays.stream(new DynamicTest[] {
                            DynamicTest.dynamicTest("test - " + num, executable),
                            DynamicTest.dynamicTest("test - " + num, executable)
                    });

                    return DynamicContainer.dynamicContainer("Outer container - " + num,
                            // 这里再嵌一层DynamicContainer
                            Stream.of(DynamicContainer.dynamicContainer(
                                    "Inner container - " + num, dynamicTestStream))
                    );
                });
    }

运行结果:

61e5d4e5c47bedc2a50ee0651a1a4358.png

总的来说,这种使用DynamicContainer嵌套的方式跟@Nested很像。

软件版本

软件版本
JUnit5.6.2(junit-jupiter)

结语

至此JUnit5动态测试就说完了,要想深入理解还得结合源码和实例。透过源码可以发现,DynamicContainer实质上并没有执行的入口,所以就像它的名称所宣示的那样,单纯就是个容器!最里层必须要有DynamicTest来告知具体的测试逻辑。另外补充一点:动态测试方法不具备普通测试方法的生命周期,这意味着动态测试方法执行前后不会执行@BeforeEach@AfterEach方法,不过@TestFactory工厂方法本身除外

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值