扩展机制

概述

JUnit 5 提供了标准的扩展机制来允许开发人员对 JUnit 5 的功能进行增强。JUnit 5 提供了很多的标准扩展接口,第三方可以直接实现这些接口来提供自定义的行为。通过@ExtendWith 注解可以声明在测试方法和类的执行中启用相应的扩展。 扩展的启用是继承的,这既包括测试类本身的层次结构,也包括测试类中的测试方法。也就是说,测试类会继承其父类中的扩展,测试方法会继承其所在类中的扩展。除此之外,在一个测试上下文中,每一个扩展只能出现一次。

extension扩展

扩展可以通过@ExtendWith或通过Java的ServiceLoader机制自动注册。

声明式注册Extension

开发人员可以通过使用@ExtendWith(...)注释测试接口,测试类,测试方法或定制组合注释来声明性地注册一个或多个扩展,并为扩展注册提供类引用。

例如,要为特定的测试方法注册自定义MockitoExtension,您将按照以下方式注解测试方法。

@ExtendWith(MockitoExtension.class)
@Test
void mockTest() {
    // ...
}

要为特定类及其子类中的所有测试注册自定义MockitoExtension,您将按照以下方式对测试类进行注解。

@ExtendWith(MockitoExtension.class)
class MockTests {
    // ...
}

多个扩展可以一起注册:

@ExtendWith({ FooExtension.class, BarExtension.class })
class MyTestsV1 {
    // ...
}

也可以这样注册:

@ExtendWith(FooExtension.class)
@ExtendWith(BarExtension.class)
class MyTestsV2 {
    // ...
}

MyTestsV1和MyTestsV2中的测试执行将按照顺序扩展为FooExtension和BarExtension。

自动注册Extension

除了使用注解的声明式注册Extension支持外,JUnit Jupiter还通过Java的java.util.ServiceLoader机制支持全局扩展注册,允许根据classpath中可用的内容自动检测并自动注册第三方扩展。

具体来说,可以通过在JAR文件中的/META-INF/services文件夹中的名为org.junit.jupiter.api.extension.Extension的文件中提供其全限定类名来注册自定义扩展。

启用自动Extension检测

自动检测是一种高级功能,因此默认情况下不启用。 要启用它,只需将junit.jupiter.extensions.autodetection.enabled配置参数设置为true即可。 这可以作为JVM系统属性提供,作为LauncherDiscoveryRequest中传递给Launcher的配置参数,或通过JUnit Platform配置文件提供。 例如,要启用扩展的自动检测,可以使用以下系统属性启动JVM。

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

当启用自动检测时,通过ServiceLoader机制发现的扩展将在JUnit Jupiter的全局Extendsion(例如,支持TestInfo,TestReporter等)之后添加到扩展注册表中。

Extension继承

注册的Extension名在具有自顶向下语义的测试类层次结构中继承。 类似地,在类级别注册的扩展在方法级继承。 此外,特定的扩展实现只能在给定的扩展上下文及其父上下文中注册一次。 因此,任何注册重复扩展实现的尝试都将被忽略。

测试执行的条件

ExecutionCondition定义了用于程序化,条件测试执行的Extension API。

对每个容器(例如,测试类)评估执行条件,以确定是否应根据提供的ExtensionContext执行其包含的所有测试。 类似地,对每个测试评估一个ExecutionCondition,以确定是否应该根据提供的ExtensionContext执行给定的测试方法。

当注册了多个ExecutionCondition扩展时,只要其中一个条件返回disabled,容器或测试就被disabled。 因此,不能保证评估条件,因为另一个扩展可能已经导致容器或测试被disabled。 换句话说,评估工作类似于短路的布尔OR运算符。

有关具体示例,请参阅DisabledCondition@Disabled的源代码。

以上都是官网的内容,看不太懂,于是乎我去看代码,翻资料。 其实就是junit 5现在支持指定在满足某些条件下才运行测试.接下来给个例子方便大家理解

写一个自定义条件,这个条件是:如果该方法被@Tag("Api")注解了就disabled掉

/**
 * @author zsh 2017/10/3
 */
public class DisableAPITests implements ExecutionCondition {
    private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult.enabled(
            "@Tag is not present");

    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        Optional<AnnotatedElement> element = context.getElement();
        Optional<Tag> disabled = findAnnotation(element, Tag.class);
        if (disabled.isPresent()) {
            String reason = disabled.map(Tag::value).filter(StringUtils::isNotBlank).filter(value->value.equals
                    ("Api")).orElseGet(()->element.get()+"api tag can't run");
            return ConditionEvaluationResult.disabled(reason+" can't run");
        }


        return ENABLED;
    }
}

使用它

/**
 * @author zsh 2017/10/1
 */
@ExtendWith(DisableAPITests.class)
class PersonTest {
    @InjectMocks
    private Person person;

    @Mock
    private Age age;
    @BeforeEach
    void setUp() {
        person = new Person();
        MockitoAnnotations.initMocks(this);
    }

    @AfterEach
    void tearDown() {
        reset(age);
    }

    @Test
    @Tag("Api")
    void addAge() {
        System.out.println("111111");
        person.addAge();
        Mockito.verify(age).setA(1);
    }

    @Test
    void addAge2() {
        System.out.println("2222222");
    }
}

addAge()方法被disabled 输出结果如下:

Api can't run
2222222

停用条件

在某些时候,执行的测试套件,在没有某些测试条件的情况下任是有效的.例如,你希望运行注解了@Disabled的方法,以便查看他们现在是否可以正确运行.为此,只需提供一个用于junit.jupiter.conditions.deactivate配置参数的模式,以指定为当前测试运行停用(即未评估)哪些条件。 该模式可以作为JVM系统属性提供,作为LauncherDiscoveryRequest中的配置参数传递给Launcher,或通过JUnit Platform配置文件提供

例如,要停用JUnit的@Disabled条件,可以使用以下系统属性启动JVM。

-Djunit.jupiter.conditions.deactivate= org.junit。* DisabledCondition

简单来说,就是Junit 5支持将Condition禁用

模式匹配语法

如果junit.jupiter.conditions.deactivate模式仅由星号()组成,则所有条件都将被禁用。 否则,该模式将用于匹配每个注册条件的完全限定类名(FQCN)。 图案中的任何点(。)将与FQCN中的点(。)或美元符号($)匹配。 任何星号()将与FQCN中的一个或多个字符匹配。 该模式中的所有其他字符将与FQCN一对一匹配。

例子:

  • *:禁用所有条件
  • org.junit.*:停用org.junit基础包及其任何子包下的每个条件。
  • *.MyCondition:取消类名的后缀名为MyCondition的每个条件。
  • System: 停用其简单类名称包含system的每个条件。
  • org.example.MyCondition: 停用其全限定名为org.example.MyCondition的条件。

后处理测试实例

通过TestInstancePostProcessor可以对测试实例添加后处理的逻辑,从而进一步对实例进行定制,比如可以通过依赖注入的方式来设置其中的属性,或是添加额外的初始化逻辑等。

通常的用例包括将依赖关系注入到测试实例中,在测试实例上调用自定义初始化方法等。

具体的例子请参考MockitoExtensionSpringExtension的源代码。

官网资料说的云里雾里.接下来用个例子帮助大家离家

定义自己的TestInstancePostProcessor

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;

/**
 * @author zsh 2017/10/3
 */
public class DiplayNameShow implements TestInstancePostProcessor {
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        System.out.println(context.getDisplayName());
    }
}

使用自己的TestInstancePostProcessor

/**
 * @author zsh 2017/10/1
 */
@ExtendWith(DiplayNameShow.class)
class PersonTest {
    @InjectMocks
    private Person person;

    @Mock
    private Age age;
    @BeforeEach
    void setUp() {
        person = new Person();
        MockitoAnnotations.initMocks(this);
    }

    @AfterEach
    void tearDown() {
        reset(age);
    }

    @Test
    void addAge() {
        System.out.println("=================");
        person.addAge();
        Mockito.verify(age).setA(1);
    }


}

结果如下:

PersonTest
=================

参数解析

ParameterResolver定义了用于在运行时动态解析参数的Extension API。

如果测试构造函数或@Test,@TestFactory,@BeforeEach,@AfterEach,@BeforeAll或@AfterAll注解的方法接受一个参数,参数必须在运行时由ParameterResolver解析。 ParameterResolver可以内置(见TestInfoParameterResolver)或用户注册。 一般来说,参数可以通过名称,类型,注解或其任何组合来解决。 具体的例子请参考CustomTypeParameterResolverCustomAnnotationParameterResolver的源代码。

/**
 * @author zsh 2017/10/3
 */
public class ApiResolver implements ParameterResolver {
    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
            ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType() == String.class
                && parameterContext.getIndex() == 0;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
            ExtensionContext extensionContext) throws ParameterResolutionException {
        return "Api";
    }
}

@ExtendWith(ApiResolver.class)
class PersonTest {

    @Test
    void addAge(final String api) {
        assertThat(api).isEqualTo("Api");
    }

}

测试生命周期回调

JUnit 5 提供了一系列与测试执行过程相关的回调方法,在测试执行中的不同阶段,运行自定义的逻辑。这些回调方法可以用来做一些与日志和性能分析的任务。总共有如下注解:

实现多个扩展API 扩展开发人员可以选择在单个扩展中实现任意数量的这些接口。 参考SpringExtension的源代码一个具体的例子。

在测试方法运行之前或之后执行

BeforeTestExecutionCallback和AfterTestExecutionCallback都是扩展的定制API,分别希望添加将在执行测试方法之前和之后立即执行的行为。 因此,这些回调非常适合于定时,跟踪和类似的用例。 如果需要实现在@BeforeEach和@AfterEach方法下调用的回调,请改用BeforeEachCallback和AfterEachCallback。

以下示例显示如何使用这些回调来计算和记录测试方法的执行时间。 TimingExtension实现了BeforeTestExecutionCallback和AfterTestExecutionCallback之间的时间和日志测试执行。

import java.lang.reflect.Method;
import java.util.logging.Logger;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

    private static final Logger LOG = Logger.getLogger(TimingExtension.class.getName());

    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        getStore(context).put(context.getRequiredTestMethod(), System.currentTimeMillis());
    }

    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        Method testMethod = context.getRequiredTestMethod();
        long start = getStore(context).remove(testMethod, long.class);
        long duration = System.currentTimeMillis() - start;

        LOG.info(() -> String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
    }

    private Store getStore(ExtensionContext context) {
        return context.getStore(Namespace.create(getClass(), context));
    }

}

由于TimingExtensionTests类通过@ExtendWith注册TimingExtension,所以它的测试将在执行时应用这个timing。

@ExtendWith(TimingExtension.class)
class TimingExtensionTests {

    @Test
    void sleep20ms() throws Exception {
        Thread.sleep(20);
    }

    @Test
    void sleep50ms() throws Exception {
        Thread.sleep(50);
    }

}

以下是运行TimingExtensionTests时生成的日志记录的示例。

INFO: Method [sleep20ms] took 24 ms.
INFO: Method [sleep50ms] took 53 ms.

异常处理

[TestExecutionExceptionHandler]是Extension的API,希望处理在测试执行期间抛出的异常。

以下示例显示一个扩展名,它将吞并IOException的所有实例,但会重新抛出任何其他类型的异常。

public class IgnoreIOExceptionExtension implements TestExecutionExceptionHandler {

    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
            throws Throwable {

        if (throwable instanceof IOException) {
            return;
        }
        throw throwable;
    }
}

提供测试模板的调用上下文

@TestTemplate方法只能在至少一个TestTemplateInvocationContextProvider注册时执行。 每个这样的提供者负责提供TestTemplateInvocationContext实例流。 每个上下文都可以指定自定义显示名称和仅用于下一次调用@TestTemplate方法的附加扩展名列表。

以下示例显示了如何编写测试模板以及如何注册和实现TestTemplateInvocationContextProvider。

@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
void testTemplate(String parameter) {
    assertEquals(3, parameter.length());
}

static class MyTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return true;
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        return Stream.of(invocationContext("foo"), invocationContext("bar"));
    }

    private TestTemplateInvocationContext invocationContext(String parameter) {
        return new TestTemplateInvocationContext() {
            @Override
            public String getDisplayName(int invocationIndex) {
                return parameter;
            }

            @Override
            public List<Extension> getAdditionalExtensions() {
                return Collections.singletonList(new ParameterResolver() {
                    @Override
                    public boolean supportsParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameterContext.getParameter().getType().equals(String.class);
                    }

                    @Override
                    public Object resolveParameter(ParameterContext parameterContext,
                            ExtensionContext extensionContext) {
                        return parameter;
                    }
                });
            }
        };
    }
}

在这个例子中,测试模板将被调用两次。 调用的显示名称将是调用上下文指定的“foo”和“bar”。 每个调用都会注册一个用于解析该方法参数的自定义ParameterResolver。 使用ConsoleLauncher时的输出如下。

└─ testTemplate(String) ✔
   ├─ foo ✔
   └─ bar ✔

TestTemplateInvocationContextProvider Extendsion API主要用于实现不同类型的测试,这些测试依赖于重复调用类似于测试的方法,尽管在不同的上下文中(例如,通过不同的参数),通过不同的准备测试类实例,或多次而不修改上下文.

保持Extendsion状态

通常,一个Extension只被实例化一次。 所以这个问题变得相关:你如何将状态从一个调用扩展到下一个? ExtensionContext API为此提供了一个存储(store)。 Extendsion可以将值放入store以供以后检索。 有关使用store与方法级范围的示例,请参阅TimingExtension。 重要的是要记住,在测试执行期间存储在ExtensionContext中的值在周围的ExtensionContext中将不可用。 由于ExtensionContexts可能嵌套,因此内部上下文的范围也可能受到限制。

扩展中支持的实用程序

JUnit Platform Commons工件公开了一个名为org.junit.platform.commons.support的包,该包包含用于处理注解,反射和classpath扫描任务的维护实用方法。 鼓励TestEngine和Extension作者使用这些支持的方法,以便与JUnit Platform的行为保持一致。

用户代码和Extendsion的相对执行顺序

当执行包含一个或多个测试方法的测试类时,除了用户提供的测试和生命周期方法之外,还会调用多个扩展回调。 下图说明了用户提供的代码和扩展代码的相对顺序。

用户提供的测试和生命周期方法以橙色显示,回调代码由蓝色显示。 灰色框表示单个测试方法的执行,并将在测试类中对每个测试方法重复执行。

下表进一步说明了用户代码和扩展代码图中的十二个步骤。

步骤 接口/注解 描述
1 interface org.junit.jupiter.api.extension.BeforeAllCallback 执行所有容器测试之前执行的扩展代码
2 annotation org.junit.jupiter.api.BeforeAll 执行所有容器测试之前执行的用户代码
3 interface org.junit.jupiter.api.extension.BeforeEachCallback 在执行每个测试之前执行的扩展代码
4 annotation org.junit.jupiter.api.BeforeEach 执行每个测试之前执行的用户代码
5 interface org.junit.jupiter.api.extension.BeforeTestExecutionCallback 在执行测试之前执行的扩展代码
6 annotation org.junit.jupiter.api.Test 用户代码的实际测试方法
7 interface org.junit.jupiter.api.extension.TestExecutionExceptionHandler 用于处理测试期间抛出的异常的扩展代码
8 interface org.junit.jupiter.api.extension.AfterTestExecutionCallback 测试执行后立即执行扩展代码及其相应的异常处理程序
9 annotation org.junit.jupiter.api.AfterEach 执行每次测试后执行的用户代码
10 interface org.junit.jupiter.api.extension.AfterEachCallback 执行每个测试后执行的扩展代码
11 annotation org.junit.jupiter.api.AfterAll 执行每个测试后执行的扩展代码
12 interface org.junit.jupiter.api.extension.AfterAllCallback 执行所有容器测试后执行的扩展代码

在最简单的情况下,仅执行实际的测试方法(步骤6); 所有其他步骤都是可选的,具体取决于用户代码的存在或相应生命周期回调的扩展支持。 有关各种生命周期回调的更多详细信息,请参阅相应的JavaDoc以获取每个注解和扩展名。

Copyright © www.gitbook.com/@VitZhou 2016 all right reserved,powered by Gitbook该文件修订时间: 2018-02-07 08:02:45

results matching ""

    No results matching ""