class ts 扩展方法_JUnit 5自定义扩展

fe8da592ce6fd42041aaf14c60c4b0d5.png

前言

org.junit.jupiter.api.extension包下,JUnit5提供了丰富的扩展接口,通过实现这些接口,我们可以定制自己的扩展并注册到JUnit中来实现功能扩展。

Extension接口

这个接口可以说是JUnit扩展中最为重要的接口,不过与它的重要性不相匹配的是,它没有定义任何的成员:

public interface Extension { }

然而它的子接口却极其丰富:

8209ac4609124e8dd9d7a64e10891552.png

注册扩展的方式

前面说过,我们可以通过实现Extension的子接口来定制自己的扩展,那么注册又是如何做到的呢?在JUnit5中提供了两个注解来帮助我们将自定义扩展注册到JUnit,它们分别是:@ExtendWith@RegisterExtension

  • @ExtendWith

这个注解可标注在类型或方法上,通过声明式的方式注册扩展。是可重复注解的同时,又接受一个Extension类型的数组,当存在多个扩展时,你可在数组中传入多个Extension,抑或是结合@Extensions注解使用:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(Extensions.class)
public @interface ExtendWith {
    Class<? extends Extension>[] value();
}
  • @RegisterExtension

这个注解也是用来注册扩展的,但是它只能被用到字段上,且相对于@ExtendWith的声明式注册方式,它是编程式的注册方式。被它标注的字段可以是staticnon-static,但这个字段不能被private修饰,实际的值也不能为null

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RegisterExtension {
}

另外,对于被static修饰和不被static修饰也要分情况讨论:

  • static:通过这种方式注册的扩展不受任何限制,你可以注册BeforeAllCallbackAfterAllCallbackTestInstanceFactoryTestInstancePostProcessorTestInstancePreDestroyCallbackBeforeEachCallback等等类型。
  • non-static:这种实例类型的注册往往会被延迟,它会在测试类实例化完成并且方法级别的@ExtendWith都注册之后,这回导致一些类级别或实例级别的扩展如:BeforeAllCallbackAfterAllCallbackTestInstanceFactoryTestInstancePostProcessor的注册出现问题。但是,如果你在测试类上标注了@TestInstance(Lifecycle.PER_CLASS),那么它将会被提前到方法级别的@ExtendWith注册之前注册。

@RegisterExtension标注的字段注册顺序是根据算法确定的,如果你想要明确他们的注册顺序,可以使用org.junit.jupiter.api.Order注解指定。

声明式 Vs 编程式

现在有必要考虑一个问题:声明式的注册意味着什么?为什么需要编程式的注册?

首先声明式注册的扩展都不支持手动调用扩展类的构造方法或工厂方法等传入参数,意味着它们的行为将无法定制,因而存在一定的局限性;由此才引出了编程式的注册方法,我们可以通过扩展的构造器或者工厂方法等传入参数,从而实现进一步的扩展定制!

这样一来我们便可知道,声明式有它的便利之处,而编程式也有它的强大之处,没有孰优孰劣,都有自己的适用场景。

注册示例

JUnit支持我们对异常做扩展,一个测试方法中可能会抛出异常,而这异常会被JUnit捕获。通过Extension下的子接口TestExecutionExceptionHandler,我们可以决定在捕获异常之后的行为。

由于@ExtendWith使用简单,且在其它文章中已多次用到,这里就不再赘述,只讲@RegisterExtension。先在测试类中创建一个ExceptionHandler

static class ExceptionHandler implements TestExecutionExceptionHandler {
        private String message;

        ExceptionHandler(String message) {
            this.message = message;
        }

        @Override
        public void handleTestExecutionException(
                ExtensionContext context, Throwable throwable) throws Throwable {
            throw new RuntimeException(String.format(
                    "Display name: [%s], reason: [%s], raw message: [%s]",
                    context.getDisplayName(), this.message, throwable.getMessage()));
        }
    }

之后通过字段方式注册它,同时写一个普通的测试方法,抛出一个异常:

class JunitSampleTests {
    static class ExceptionHandler implements TestExecutionExceptionHandler {
        // ...
    }
    @RegisterExtension
    static ExceptionHandler exceptionHandler = new ExceptionHandler("异常被捕获了");

    @Test
    void test() {
        throw new RuntimeException("测试出错啦!");
    }
}

注意上面调用了ExceptionHandler的构造器传入了message,这是声明式的@ExtendWith无法做到的:

3ac6bff9bcb6fe50086309664818a17a.png

SPI注册

不知你是否了解过Java中的SPI(Service Provider Interface)机制,它可以用来启用框架扩展和替换组件,这在JUnit中是支持的,借此我们可以在不使用注解的情况下注册我们的扩展。

如果你是Maven工程,需要在test目录下的测试资源目录resources(这个目录一般不存在,需要手动创建并右键标记为“Test Resources Root”)中新建一个名为junit-platform.properties的文件(这个文件名不能随意,否则JUnit无法扫描到),并在其中加上配置:

junit.jupiter.extensions.autodetection.enabled=true

而后同样在这个resources下,新建目录WEB-INF/services,之后创建一个名为org.junit.jupiter.api.extension.Extension的文件,这个文件名其实就是Extension接口的全限定类名,而其中的内容就是我们实现的扩展类的全限定类名(如有多个扩展实现,一个限定名占一行):

com.tinysand.fileuploads.ExceptionHandler

这里我把之前的扩展实现单独提成了一个类文件,同时去掉了带参的构造器和成员,显然这种方式也只能像@ExtendWith注解做的那样。

最后,如果不想使用配置文件,你可以在VMRun/Debug Configurations中的VM options)参数添加如下参数而达到同样的效果:

-Djunit.jupiter.extensions.autodetection.enabled=true

软件版本

软件版本
JUnit5.6.2(junit-jupiter)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值