JUnit5用户手册

JUnit5用户手册

此文为对JUnit5官网用户指南的学习,版本为5.4.0版本,对部分内容进行翻译、截取和自我理解,官网手册地址为https://junit.org/junit5/docs/current/user-guide/。 (中文的翻译是5.3版本的)html

1总览

JUnit5最低须要JDK8来支持。java

与以前的版本不一样,JUnit5由3个模块组成:git

一、JUnit Platform,用于在JVM上启动测试框架,并经过命令行定义TestEngine API。能够至关于JUnit 4 中的Runner ,要用他作测试引擎。简单地说这个有关的包是用来调用测试用例的,IDE正式由于加载了与这个有关的插件,因此idea里边才能够右键选择执行测试方法。github

二、JUnit Jupiter是用于在JUnit 5中编写测试和扩展的新编程模型和扩展模型的组合。提供了一堆测试要用的注解和类。web

三、Junit Vintage,用于在JUnit5平台上运行JUnit3和4测试用例。正则表达式

1.1. JUnit Platform

Group ID: org.junit.platformspring

Version: 1.2.0编程

Artifact IDs:api

junit-platform-commons数组

JUnit 内部通用类库/实用工具,它们仅用于JUnit框架自己,不支持任何外部使用,外部使用风险自负。

junit-platform-console

支持从控制台中发现和执行JUnit Platform上的测试。详情请参阅 控制台启动器

junit-platform-console-standalone

一个包含了Maven仓库中的 junit-platform-console-standalone 目录下全部依赖项的可执行JAR包。详情请参阅 控制台启动器

junit-platform-engine

测试引擎的公共API。详情请参阅 插入你本身的测试引擎

junit-platform-gradle-plugin

支持使用 Gralde 来发现和执行JUnit Platform上的测试。

junit-platform-launcher

配置和加载测试计划的公共API – 典型的使用场景是IDE和构建工具。详情请参阅 JUnit Platform启动器API

junit-platform-runner

在一个JUnit 4环境中的JUnit Platform上执行测试和测试套件的运行器。详情请参阅 使用JUnit 4运行JUnit Platform

junit-platform-suite-api

在JUnit Platform上配置测试套件的注解。被 JUnit Platform运行器 所支持,也有可能被第三方的TestEngine实现所支持。

junit-platform-surefire-provider

支持使用 Maven Surefire 来发现和执行JUnit Platform上的测试。

1.2. JUnit Jupiter

Group ID: org.junit.jupiter

Version: 5.3.0

Artifact IDs:

junit-jupiter-api

编写测试 和 扩展 的JUnit Jupiter API。

junit-jupiter-engine

JUnit Jupiter测试引擎的实现,仅仅在运行时须要。

junit-jupiter-params

支持JUnit Jupiter中的 参数化测试

junit-jupiter-migration-support

支持从JUnit 4迁移到JUnit Jupiter,仅在使用了JUnit 4规则的测试中才须要。

1.3. JUnit Vintage

Group ID: org.junit.vintage

Version: 5.3.0

Artifact ID:

junit-vintage-engine

JUnit Vintage测试引擎实现,容许在新的JUnit Platform上运行低版本的JUnit测试,即那些以JUnit 3或JUnit 4风格编写的测试。

 

2编写测试

package TestJunit5;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class _1_test {

    @Test

    void testIntAddition() {

        assertEquals(2, 1 + 1);

    }

}

2.1 注解

除非另有说明,不然全部核心注释都位于junit-jupiter-api模块的org.junit.jupiter.api包中。

注解

描述

@Test

表示该方法是一个测试方法。与JUnit 4的@Test注解不一样的是,它没有声明任何属性,由于JUnit Jupiter中的测试扩展是基于它们本身的专用注解来完成的。这样的方法会被继承,除非它们被覆盖

@ParameterizedTest

表示该方法是一个 参数化测试。这样的方法会被继承,除非它们被覆盖

@RepeatedTest

表示该方法是一个 重复测试 的测试模板。这样的方法会被继承,除非它们被覆盖

@TestFactory

表示该方法是一个 动态测试 的测试工厂。这样的方法会被继承,除非它们被覆盖

@TestInstance

用于配置所标注的测试类的 测试实例生命周期。这些注解会被继承

@TestTemplate

表示该方法是一个 测试模板,它会依据注册的 提供者 所返回的调用上下文的数量被屡次调用。 这样的方法会被继承,除非它们被覆盖

@TestMethodOrder

用于配置带注释的测试类的测试方法执行顺序;相似于JUnit 4的@FixMethodOrder。注解能够被继承。

@DisplayName

为测试类或测试方法声明一个自定义的显示名称。该注解不能被继承

@DisplayNameGeneration

声明测试类的自定义显示名称生成器。注解会被继承。

@BeforeEach

表示使用了该注解的方法应该在当前类中每个使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法以前 执行;相似于JUnit 4的 @Before。这样的方法会被继承,除非它们被覆盖

@AfterEach

表示使用了该注解的方法应该在当前类中每个使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法以后 执行;相似于JUnit 4的 @After。这样的方法会被继承,除非它们被覆盖

@BeforeAll

表示使用了该注解的方法应该在当前类中全部使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法以前 执行;相似于JUnit 4的 @BeforeClass。这样的方法会被继承(除非它们被隐藏 或覆盖),而且它必须是 static方法(除非"per-class" 测试实例生命周期 被使用)。

@AfterAll

表示使用了该注解的方法应该在当前类中全部使用了@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法以后执行;相似于JUnit 4的 @AfterClass。这样的方法会被继承(除非它们被隐藏 或覆盖),而且它必须是 static方法(除非"per-class" 测试实例生命周期 被使用)。

@Nested

表示使用了该注解的类是一个内嵌、非静态的测试类。@BeforeAll和@AfterAll方法不能直接在@Nested测试类中使用,(除非"per-class" 测试实例生命周期 被使用)。该注解不能被继承

@Tag

用于声明过滤测试的tags,该注解能够用在方法或类上;相似于TesgNG的测试组或JUnit 4的分类。该注解能被继承,但仅限于类级别,而非方法级别。

@Disable

用于禁用一个测试类或测试方法;相似于JUnit 4的@Ignore。该注解不能被继承。

@ExtendWith

用于注册自定义 扩展。该注解不能被继承

@RegisterExtension

用于经过属性以编程方式注册扩展。这些属性是继承的,除非它们被(子类)屏蔽。

@TempDir

用于经过生命周期方法或测试方法中的字段注入或参数注入来提供临时目录。位于org.junit.jupiter.api.io包中。

还有一些注解在实验中。具体能够看Experimental APIs 表。

2.2 测试类和测试方法

测试类和测试方法能够不为public,但必须不为private。

这里先记得@BeforeAll和@AfterAll修饰的是静态方法

2.3 显示名称

这里@DisplayName注解用来标注名称,方便查看。

@DisplayNameGeneration,是一个用来生成DisplayName的注解,配合DisplayNameGenerator类使用。@DisplayName注解的优先级更高

class DisplayNameGeneratorDemo {

 

    @Nested

    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)

    class A_year_is_not_supported {

        @Test

        void if_it_is_zero() {

        }

        @DisplayName("A negative value for year is not supported by the leap year computation.")

        @ParameterizedTest(name = "For example, year {0} is not supported.")

        @ValueSource(ints = {1, -4})

        void if_it_is_negative(int year) {

        }

    }

}

生成结果以下,能够看到使用这个注解以后,测试以后的名称下划线消失了,这是由于传入了DisplayNameGenerator.ReplaceUnderscores.class,而后能够看到的确是@DisplayName的优先级更高一些,下面的方法的名称是@DisplayName传入进去的。

而后若是想要自定义名称生成的规则,能够去继承内部的ReplaceUnderscores 进行改进。

 

 

2.4 断言

全部的JUnit Jupiter断言都是 org.junit.jupiter.api.Assertions类中static方法。可使用Lambda表达式。

而后若是断言不能知足要求,能够导入第三方的断言库。

2.5 假设

假设与断言的区别:假设失败则中止测试,断言失败则抛出错误。假设能够配合断言使用

@Test

    void testInAllEnvironments() {

        assumingThat("CI".equals(System.getenv("ENV")),

                () -> {

                    // 假设成立才去执行断言

                    assertEquals(2, 2);

                });

        // perform these assertions in all environments

        assertEquals(42, 42);

    }

2.6 禁用测试

@Disabled能够禁用测试,再或者经过自定义的 ExecutionCondition 来禁用 整个测试类或单个测试方法。

2.7 有条件的执行测试

JUnit Jupiter中的 ExecutionCondition 扩展API容许开发人员以编程的方式基于某些条件启用或禁用容器或测试。这种状况的最简单示例是内置的 DisabledCondition,它支持 @Disabled 注解(请参阅 禁用测试)。除了@Disabled以外,JUnit Jupiter还支持 org.junit.jupiter.api.condition包中的其余几个基于注解的条件,容许开发人员以 声明的方式启用或禁用容器和测试。

2.7.1 操做系统条件

能够经过 @EnabledOnOs 和 @DisabledOnOs 注释在特定操做系统上启用或禁用容器或测试。

2.7.2 操做系统条件

能够经过 @EnabledOnJre 和 @DisabledOnJre 注解在特定版本的Java运行时环境(JRE)上启用或禁用容器或测试。

2.7.3 系统属性条件

能够经过 @EnabledIfSystemProperty 和 @DisabledIfSystemProperty 注解根据指定的JVM系统属性的值启用或禁用容器或测试。经过matches属性提供的值将被解释为正则表达式。

2.7.5 脚本条件

根据对经过 @EnabledIf 或 [@DisabledIf](https://junit.org/junit5/docs/5.3.0/api/org/junit/jupiter/api/condition/DisabledIf.html) 注解配置的脚本的评估,JUnit Jupiter提供了 启用或禁用 容器或测试的功能。脚本能够用JavaScript,Groovy或任何其余支持Java脚本API的脚本语言编写,由JSR 223定义。

脚本绑定

如下名称绑定到每一个脚本上下文,所以在脚本中使用。访问器 经过简单的String get(String name)方法提供对相似Map结构的访问。

名称

类型

描述

systemEnvironment

accessor

操做系统环境变量访问器。

systemProperty

accessor

JVM 系统属性访问器。

JunitConfigurationParameter

accessor

配置参数访问器。

JunitDisplayName

String

测试或容器的显示名称。

junitTags

Set<String>

全部分配给测试或容器的标记。

junitUniqueId

String

测试或容器的惟一ID。

2.8 标记和过滤

能够用@Tag标签标记,而后后面能够根据里边传入的信息对测试进行发现和过滤。

标记的限制(trimmed指两端空格被去掉)

标记不能为null或

trimmed 的标记不能包含空格。

trimmed 的标记不能包含IOS字符。

trimmed 的标记不能包含一下保留字符。

,:逗号

(:左括号

):右括号

&:& 符号

|:竖线

!:感叹号

2.9 测试执行顺序

能够在类上标注@TestMethodOrder来声明测试方法要有执行顺序,里边能够传入三种类AlphanumericOrderAnnotationRandom,分别表明字母排序、数字排序、随机。而后对方法加@Order注解里边传入参数决定顺序。

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;

import org.junit.jupiter.api.Order;

import org.junit.jupiter.api.Test;

import org.junit.jupiter.api.TestMethodOrder;

 

@TestMethodOrder(OrderAnnotation.class)

class OrderedTestsDemo {

    @Test

    @Order(1)

    void nullValues() {

        // perform assertions against null values

    }

    @Test

    @Order(2)

    void emptyValues() {

        // perform assertions against empty values

    }

    @Test

    @Order(3)

    void validValues() {

        // perform assertions against valid values

    }

}

2.10 测试实例生命周期

为了隔离地执行单个测试方法,以及避免因为不稳定的测试实例状态引起非预期的反作用,JUnit会在执行每一个测试方法执行以前建立一个新的实例。这个”per-method”测试实例生命周期是JUnit Jupiter的默认行为,这点相似于JUnit之前的全部版本。

若是你但愿JUnit Jupiter在同一个实例上执行全部的测试方法,在你的测试类上加上注解@TestInstance(Lifecycle.PER_CLASS)便可。启用了该模式后,每个测试类只会建立一次实例。所以,若是你的测试方法依赖实例变量存储的状态,你可能须要在@BeforeEach或@AfterEach方法中重置状态。

"per-class"模式相比于默认的"per-method"模式有一些额外的好处。具体来讲,使用了"per-class"模式以后,你就能够在非静态方法和接口的default方法上声明@BeforeAll和 @AfterAll。所以,"per-class"模式使得在@Nested测试类中使用@BeforeAll和@AfterAll注解成为了可能。

2.10.1 更改默认的测试实例生命周期

若是测试类或测试接口上没有使用@TestInstance注解,JUnit Jupiter 将使用默认 的生命周期模式。标准的默认 模式是PER_METHOD。然而,整个测试计划执行的默认值 是能够被更改的。要更改默认测试实例生命周期模式,只需将junit.jupiter.testinstance.lifecycle.default配置参数 设置为定义在TestInstance.Lifecycle中的枚举常量名称便可,名称忽略大小写。它也做为一个JVM系统属性、做为一个传递给Launcher的LauncherDiscoveryRequest中的配置参数、或经过JUnit Platform配置文件来提供(详细信息请参阅 配置参数)。

例如,要将默认测试实例生命周期模式设置为Lifecycle.PER_CLASS,你可使用如下系统属性启动JVM。

-Djunit.jupiter.testinstance.lifecycle.default=per_class

可是请注意,经过JUnit Platform配置文件来设置默认的测试实例生命周期模式是一个更强大的解决方案,由于配置文件能够与项目一块儿被提交到版本控制系统中,所以可用于IDE和构建软件。

要经过JUnit Platform配置文件将默认测试实例生命周期模式设置为Lifecycle.PER_CLASS,你须要在类路径的根目录(例如,src/test/resources)中建立一个名为junit-platform.properties的文件,并写入如下内容。

junit.jupiter.testinstance.lifecycle.default = per_class

2.11 嵌套测试

对应@Nested注解。没有这个注解的话他就不测试内部类了。

@Nested测试类必须是非静态嵌套类(即内部类),而且能够有任意多层的嵌套。这些内部类被认为是测试类家族的正式成员,但有一个例外:@BeforeAll和@AfterAll方法默认 不会工做。缘由是Java不容许内部类中存在static成员。不过这种限制可使用@TestInstance(Lifecycle.PER_CLASS)标注@Nested测试类来绕开。

2.12 构造器和方法的依赖注入

以前版本不行,如今能够了。

ParameterResolver 为测试扩展定义了API,它能够在运行时动态解析参数。若是一个测试的构造函数方法接收一个参数,这个参数就必须在运行时被一个已注册的ParameterResolver解析。

目前有三种被自动注册的内置解析器。

TestInfoParameterResolver:若是一个方法参数的类型是 TestInfo,TestInfoParameterResolver将根据当前的测试提供一个TestInfo的实例用于填充参数的值。而后,TestInfo就能够被用来检索关于当前测试的信息,例如:显示名称、测试类、测试方法或相关的Tag。显示名称要么是一个相似于测试类或测试方法的技术名称,要么是一个经过@DisplayName配置的自定义名称。

简单的说就是参数是TestInfo类型,那么就能够在函数中调用,这个类包含不少测试的信息。

import org.junit.jupiter.api.TestInfo;

@DisplayName("TestInfo Demo")

class TestInfoDemo {

    TestInfoDemo(TestInfo testInfo) {

        assertEquals("TestInfo Demo", testInfo.getDisplayName());

    }

    @BeforeEach

    void init(TestInfo testInfo) {

        String displayName = testInfo.getDisplayName();

        assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));

    }

    @Test

    @DisplayName("TEST 1")

    @Tag("my-tag")

    void test1(TestInfo testInfo) {

        assertEquals("TEST 1", testInfo.getDisplayName());

        assertTrue(testInfo.getTags().contains("my-tag"));

    }

    @Test

    void test2() {

    }

}

RepetitionInfoParameterResolver:若是一个位于@RepeatedTest、@BeforeEach或者@AfterEach方法的参数的类型是 RepetitionInfo,RepetitionInfoParameterResolver会提供一个RepetitionInfo实例。而后,RepetitionInfo就能够被用来检索对应@RepeatedTest方法的当前重复以及总重复次数等相关信息。可是请注意,RepetitionInfoParameterResolver不是在@RepeatedTest的上下文以外被注册的。请参阅 重复测试示例

TestInfoParameterResolver:若是一个方法参数的类型是 TestReporter,TestReporterParameterResolver会提供一个TestReporter实例。而后,TestReporter就能够被用来发布有关当前测试运行的其余数据。这些数据能够经过 TestExecutionListener 的reportingEntryPublished()方法来消费,所以能够被IDE查看或包含在报告中。

在JUnit Jupiter中,你应该使用TestReporter来代替你在JUnit 4中打印信息到stdout或stderr的习惯。使用@RunWith(JUnitPlatform.class)会将报告的全部条目都输出到stdout中。

import org.junit.jupiter.api.Test;

import org.junit.jupiter.api.TestReporter;

class TestReporterDemo {

    @Test

    void reportSingleValue(TestReporter testReporter) {

        testReporter.publishEntry("a key", "a value");

    }

    @Test

    void reportSeveralValues(TestReporter testReporter) {

        HashMap<String, String> values = new HashMap<>();

        values.put("user name", "dk38");

        values.put("award year", "1974");

        testReporter.publishEntry(values);//会在控制台输出

    }

}

其余的参数解析器必须经过@ExtendWith注册合适的 扩展 来明确地开启。

能够查看 RandomParametersExtension 获取自定义 ParameterResolver 的示例。虽然并不打算大量使用它,但它演示了扩展模型和参数解决过程当中的简单性和表现力。MyRandomParametersTest演示了如何将随机值注入到@Test方法中。

@ExtendWith(RandomParametersExtension.class)

class MyRandomParametersTest {

    @Test

    void injectsInteger(@Random int i, @Random int j) {

        assertNotEquals(i, j);

    }

    @Test

    void injectsDouble(@Random double d) {

        assertEquals(0.0, d, 1.0);

    }

}

对于真实的使用场景,请查看 MockitoExtension 和 SpringExtension 的源码。

2.13 测试接口和默认方法

JUnit Jupiter容许将@Test、@RepeatedTest、@ParameterizedTest、@TestFactory、TestTemplate、@BeforeEach和@AfterEach注解声明在接口的default方法上。若是 测试接口或测试类使用了@TestInstance(Lifecycle.PER_CLASS)注解(请参阅 测试实例生命周期),则能够在测试接口中的static方法或接口的default方法上声明@BeforeAll和@AfterAll。

能够在测试接口上声明@ExtendWith和@Tag,以便实现了该接口的类自动继承它的tags和扩展。

2.14 重复测试

@RepeatedTest中填入次数能够重复测试。

除了指定重复次数以外,咱们还能够经过@RepeatedTest注解的name属性为每次重复配置自定义的显示名称。此外,显示名称能够是由静态文本和动态占位符的组合而组成的模式。目前支持如下占位符。

{displayName}: @RepeatedTest方法的显示名称。

{currentRepetition}: 当前的重复次数。

{totalRepetitions}: 总的重复次数。

一个特定重复的默认显示名称基于如下模式生成:"repetition {currentRepetition} of {totalRepetitions}"。所以,以前的repeatTest()例子的单个重复的显示名称将是:repetition 1 of 10, repetition 2 of 10,等等。若是你但愿每一个重复的名称中包含@RepeatedTest方法的显示名称,你能够自定义本身的模式或使用预约义的RepeatedTest.LONG_DISPLAY_NAME。后者等同于"{displayName} :: repetition {currentRepetition} of {totalRepetitions}",在这种模式下,repeatedTest()方法单次重复的显示名称长成这样:repeatedTest() :: repetition 1 of 10, repeatedTest() :: repetition 2 of 10,等等。

为了以编程方式获取有关当前重复和总重复次数的信息,开发人员能够选择将一个RepetitionInfo的实例注入到@RepeatedTest,@BeforeEach或@AfterEach方法中。

    @RepeatedTest(2)

    void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {

        assertEquals(2, repetitionInfo.getTotalRepetitions());

    }

 

    @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")

    @DisplayName("Repeat!")

    void customDisplayName(TestInfo testInfo) {

        assertEquals(testInfo.getDisplayName(), "Repeat! 1/1");

    }

 

    @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)

    @DisplayName("Details...")

    void customDisplayNameWithLongPattern(TestInfo testInfo) {

        assertEquals(testInfo.getDisplayName(), "Details... :: repetition 1 of 1");

    }

 

    @RepeatedTest(value = 2, name = "Wiederholung {currentRepetition} von {totalRepetitions}")

    void repeatedTestInGerman() {

        // ...

    }

测试结果以下图所示。简单地说就是能够在注解的name属性中传入测试名与占位符,这样在测试输出的时候更容易经过名称确认是哪一个测试的哪一次。而后能够用以前的方法传参章节中的内容,经过在测试方法中传入TestInfo或RepetitionInfo类来实现测试的方法体中获取测试参数,如获取总共测试多少次,得到当前是第几回。

 

 

2.15 参数化测试

使用@ParameterizedTest注解,参数化测试使得测试能够测试屡次使用不一样的参数值。

为了使用参数化测试,你必须添加junit-jupiter-params依赖。

@ParameterizedTest

@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })

void palindromes(String candidate) {

    assertTrue(StringUtils.isPalindrome(candidate));

}

参数化测试方法一般会在参数源索引和方法参数索引之间采用一对一关联(请参阅 @CsvSource 中的示例)以后直接从配置的源中消耗参数(请参阅 参数源)。可是,参数化测试方法也能够选择未来自源的参数聚合 为传递给该方法的单个对象(请参阅 参数聚合)。其余参数也能够由ParameterResolver提供(例如,获取TestInfo,TestReporter等的实例)。具体而言,参数化测试方法必须根据如下规则声明形式参数。

首先必须声明零个或多个索引参数。

接下来必须声明零个或多个聚合器。

由ParameterResolver提供的零个或多个参数必须声明为最后一个。

2.15.1 参数源

1@ValueSource是最简单的来源之一。它容许你指定单个数组的文字值,而且只能用于为每一个参数化的测试调用提供单个参数。

@ValueSource支持如下类型的字面值:

short

byte

int

long

float

double

char

java.lang.String

java.lang.Class

为了测试一些极端状况,null啊或者为空之类的,还提供了一些额外的注解。

@NullSource注解用来给参数测试提供一个null元素,要求传参的类型不能是基本类型(基本类型不能是null值)

@EmptySource为java.lang.String, java.util.List, java.util.Set, java.util.Map, primitive arrays (e.g., int[], char[][], etc.), object arrays (e.g.,String[], Integer[][], etc.)类型提供了空参数。

@NullAndEmptySource 注解为上边两个注解的合并。

@ParameterizedTest

@NullSource

@EmptySource

@ValueSource(strings = { " ", "   ", "\t", "\n" })

void nullEmptyAndBlankStrings(String text) {

    assertTrue(text == null || text.trim().isEmpty());

}

能够看到@VauleSource中只有4个参数,但却有6个测试,另外两个是经过注解添加的。

 

 

2@EnumSource可以很方便地提供Enum常量。该注解提供了一个可选的names参数,你能够用它来指定使用哪些常量。若是省略了,就意味着全部的常量将被使用,就像下面的例子所示。

@ParameterizedTest

@EnumSource(value = TimeUnit.class, names = { "DAYS", "HOURS" })

void testWithEnumSourceInclude(TimeUnit timeUnit) {

    assertTrue(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));

}

@EnumSource注解还提供了一个可选的mode参数,它可以细粒度地控制哪些常量将会被传递到测试方法中。例如,你能够从枚举常量池中排除一些名称或者指定正则表达式,以下面代码所示。第一个exclude是排除names中的枚举类型,第二个是匹配names中的枚举类型。

    @ParameterizedTest

    @EnumSource(value = TimeUnit.class, mode = EXCLUDE, names = {"DAYS", "HOURS"})

    void testWithEnumSourceExclude(TimeUnit timeUnit) {

        assertFalse(EnumSet.of(TimeUnit.DAYS, TimeUnit.HOURS).contains(timeUnit));

        assertTrue(timeUnit.name().length() > 5);

    }

    @ParameterizedTest

    @EnumSource(value = TimeUnit.class, mode = MATCH_ALL, names = "^(M|N).+SECONDS$")

    void testWithEnumSourceRegex(TimeUnit timeUnit) {

        String name = timeUnit.name();

        assertTrue(name.startsWith("M") || name.startsWith("N"));

        assertTrue(name.endsWith("SECONDS"));

    }

3@MethodSource容许你引用测试类或外部类中的一个或多个工厂 方法。

除非使用@TestInstance(Lifecycle.PER_CLASS)注解标注测试类,不然测试类中的工厂方法必须是static的。 而外部类中的工厂方法必须始终是static的。 此外,此类工厂方法不能接受任何参数。

每一个工厂方法必须生成一个参数流,而且流中的每组参数将被做为被@ParameterizedTest标注的方法的单独调用的物理参数来提供。 通常来讲,这会转换为Arguments的Stream(即,Stream<Arguments>); 可是,实际的具体返回类型能够采用多种形式。 在此上下文中,”流?是JUnit能够可靠地转换为Stream的任何内容,例如Stream,DoubleStream,LongStream,IntStream,Collection,Iterator,Iterable,对象数组或基元数组。 流中的”参数”能够做为参数的实例,对象数组(例如,Object[])提供,或者若是参数化测试方法接受单个参数,则提供单个值。

    @ParameterizedTest

    @MethodSource("stringProvider")

    void testWithSimpleMethodSource(String argument) {

        assertNotNull(argument);

    }

 

    static Stream<String> stringProvider() {

        return Stream.of("foo", "bar");

    }

若是你未经过@MethodSource明确提供工厂方法名称,则JUnit Jupiter将按照约定去搜索与当前@ParameterizedTest方法名称相同的工厂方法。

若是参数化测试方法声明了多个参数,则须要返回Arguments实例或对象数组的集合,流或数组,以下所示(有关支持的返回类型的更多详细信息,请参阅@MethodSource的JavaDoc)。 请注意,arguments(Object ...)是Arguments接口中定义的静态工厂方法。 

    @ParameterizedTest

    @MethodSource("stringIntAndListProvider")

    void testWithMultiArgMethodSource(String str, int num, List<String> list) {

        assertEquals(3, str.length());

        assertTrue(num >=1 && num <=2);

        assertEquals(2, list.size());

    }

    static Stream<Arguments> stringIntAndListProvider() {

        return Stream.of(

                Arguments.of("foo", 1, Arrays.asList("a", "b")),

                Arguments.of("bar", 2, Arrays.asList("x", "y"))

        );

    }

此外,能够引用别的类里边的静态工厂方法。用#分隔类和方法

class ExternalMethodSourceDemo {

 

    @ParameterizedTest

    @MethodSource("example.StringsProviders#tinyStrings")

    void testWithExternalMethodSource(String tinyString) {

        // test with tiny string

    }

}

 

class StringsProviders {

 

    static Stream<String> tinyStrings() {

        return Stream.of(".", "oo", "OOO");

    }

}

4@CsvSource容许你将参数列表定义为以逗号分隔的值(即String类型的值)。

@CsvSource使用单引号'做为引用字符。请参考上述示例和下表中的'baz,qux'值。一个空的引用值''表示一个空的String;而一个彻底的值被当成一个null引用。若是null引用的目标类型是基本类型,则会抛出一个ArgumentConversionException。

示例输入

生成的参数列表

@CsvSource({ "foo, bar" })

"foo", "bar"

@CsvSource({ "foo, 'baz, qux'" })

"foo", "baz, qux"

@CsvSource({ "foo, ''" })

"foo", ""

@CsvSource({ "foo, " })

"foo", null

@ParameterizedTest

@CsvSource({ "foo, 1", "bar, 2", "'baz, qux', 3" })

void testWithCsvSource(String first, int second) {

    assertNotNull(first);

    assertNotEquals(0, second);

}

5@CsvFileSource容许你使用类路径中的CSV文件。CSV文件中的每一行都会触发参数化测试的一次调用。

@ParameterizedTest

@CsvFileSource(resources = "/two-column.csv")

void testWithCsvFileSource(String first, int second) {

    assertNotNull(first);

    assertNotEquals(0, second);

}

文件内容为:

foo, 1

bar, 2

"baz, qux", 3

 与@CsvSource中使用的语法相反,@CsvFileSource使用双引号"做为引号字符,请参考上面例子中的"baz,qux"值,一个空的带引号的值""表示一个空String,一个彻底为空的值被当成null引用,若是null引用的目标类型是基本类型,则会抛出一个ArgumentConversionException。

6@ArgumentsSource 能够用来指定一个自定义且可以复用的ArgumentsProvider

@ParameterizedTest

@ArgumentsSource(MyArgumentsProvider.class)

void testWithArgumentsSource(String argument) {

    assertNotNull(argument);

}

 

static class MyArgumentsProvider implements ArgumentsProvider {

 

    @Override

    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {

        return Stream.of("apple", "banana").map(Arguments::of);

    }

}

2.15.2 参数转换

扩辗转换

JUnit Jupiter为提供给@ParameterizedTest的参数提供了 扩展基本类型转换 的支持。例如,使用@ValueSource(ints = {1,2,3})注解的参数化测试能够声明为不只接受int类型的参数,还接受long,float或double类型的参数。

隐式转换

为了支持像@CsvSource这样的使用场景,JUnit Jupiter提供了一些内置的隐式类型转换器。转换过程取决于每一个方法参数的声明类型。

例如,若是一个@ParameterizedTest方法声明了TimeUnit类型的参数,而实际上提供了一个String,此时字符串会被自动转换成对应的TimeUnit枚举常量。

回退String-to-Object转换

除了从字符串到上表中列出的目标类型的隐式转换以外,若是目标类型只声明一个合适的工厂方法 或工厂构造函数,则JUnit Jupiter还提供了一个从String自动转换为给定目标类型的回退机制,工厂方法和工厂构造函数定义以下:

工厂方法:在目标类型中声明的非私有静态方法,它接受单个String参数并返回目标类型的实例。该方法的名称能够是任意的,不须要遵循任何特定的约定。

工厂构造函数:目标类型中的一个非私有构造函数,它接受一个String参数。

若是发现多个工厂方法,它们将被忽略。若是同时发现了工厂方法 和工厂构造函数,则将使用工厂方法 而不使用构造函数。

显式转换

除了使用隐式转换参数,你还可使用@ConvertWith注解来显式指定一个ArgumentConverter用于某个参数,例以下面代码所示。

@ParameterizedTest

@EnumSource(TimeUnit.class)

void testWithExplicitArgumentConversion(@ConvertWith(ToStringArgumentConverter.class) String argument) {

    assertNotNull(TimeUnit.valueOf(argument));

}

 

static class ToStringArgumentConverter extends SimpleArgumentConverter {

 

    @Override

    protected Object convert(Object source, Class<?> targetType) {

        assertEquals(String.class, targetType, "Can only convert to String");

        return String.valueOf(source);

    }

}

显式参数转换器意味着开发人员要本身去实现它。正由于这样,junit-jupiter-params仅仅提供了一个能够做为参考实现的显式参数转换器:JavaTimeArgumentConverter。你能够经过组合注解JavaTimeArgumentConverter来使用它。

@ParameterizedTest

@ValueSource(strings = { "01.01.2017", "31.12.2017" })

void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("dd.MM.yyyy") LocalDate argument) {

    assertEquals(2017, argument.getYear());

}

2.15.3 参数聚合

默认状况下,提供给@ParameterizedTest方法的每一个参数对应于单个方法参数。所以,指望提供大量参数的参数源可能致使大的方法签名。

在这种状况下,可使用ArgumentsAccessor而不是多个参数。使用此API,你能够经过传递给你的测试方法的单个参数去访问提供的参数。另外,它还支持类型转换,如 隐式转换中所述。

@ParameterizedTest

@CsvSource({

    "Jane, Doe, F, 1990-05-20",

    "John, Doe, M, 1990-10-22"

})

void testWithArgumentsAccessor(ArgumentsAccessor arguments) {

    Person person = new Person(arguments.getString(0),

                               arguments.getString(1),

                               arguments.get(2, Gender.class),

                               arguments.get(3, LocalDate.class));

 

    if (person.getFirstName().equals("Jane")) {

        assertEquals(Gender.F, person.getGender());

    }

    else {

        assertEquals(Gender.M, person.getGender());

    }

    assertEquals("Doe", person.getLastName());

    assertEquals(1990, person.getDateOfBirth().getYear());

}

自定义聚合器

除了使用ArgumentsAccessor直接访问@ParameterizedTest方法的参数外,JUnit Jupiter还支持使用自定义的可重用聚合器。

要使用自定义聚合器,只需实现ArgumentsAggregator接口并经过@AggregateWith注释将其注册到@ParameterizedTest方法的兼容参数中。当调用参数化测试时,聚合结果将做为相应参数的参数提供。

@ParameterizedTest

@CsvSource({

    "Jane, Doe, F, 1990-05-20",

    "John, Doe, M, 1990-10-22"

})

void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) {

    // perform assertions against person

}

 

public class PersonAggregator implements ArgumentsAggregator {

    @Override

    public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {

        return new Person(arguments.getString(0),

                          arguments.getString(1),

                          arguments.get(2, Gender.class),

                          arguments.get(3, LocalDate.class));

    }

}

若是你发现本身在代码库中为多个参数化测试方法重复声明@AggregateWith(MyTypeAggregator.class),此时你可能但愿建立一个自定义组合注解,好比@CsvToMyType,它使用@AggregateWith(MyTypeAggregator.class)进行元注解。如下示例经过自定义@CsvToPerson注解演示了这一点。

@ParameterizedTest

@CsvSource({

    "Jane, Doe, F, 1990-05-20",

    "John, Doe, M, 1990-10-22"

})

void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {

    // perform assertions against person

}

 

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.PARAMETER)

@AggregateWith(PersonAggregator.class)

public @interface CsvToPerson {

}

2.15.4 自定义显示名称

在参数化测试中,经过如下方法自定义显示方法名称。

@DisplayName("Display name of container")

@ParameterizedTest(name = "{index} ==> fruit=''{0}'', rank={1}")

@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })

void testWithCustomDisplayNames(String fruit, int rank) {

}

2.15.5 生命周期和互通性

能够在函数参数列表中混合使用多种参数,可是经过@ValueSource注解传入方法中的参数必须在首位(也能够像以前章节说的对参数使用ParameterResolver进行扩展),而下边后边的TestReporter就是前边说的方法中传入参数。

@ParameterizedTest

@ValueSource(strings = "foo")

void testWithRegularParameterResolver(String argument, TestReporter testReporter) {

    testReporter.publishEntry("argument", argument);

}

2.16 测试模板

@TestTemplate 方法不是一个常规的测试用例,它是测试用例的模板。所以,它的设计初衷是用来被屡次调用,而调用次数取决于注册提供者返回的调用上下文数量。因此,它必须结合 TestTemplateInvocationContextProvider 扩展一块儿使用。测试模板方法每一次调用跟执行常规@Test方法同样,它也彻底支持相同的生命周期回调和扩展。关于它的用例请参阅 为测试模板提供调用上下文

@RepeatedTest和@ ParameterizedTest就是固有的测试模板。

2.17 动态测试

@TestFactory注解用以实现测试的动态运行。

与@Test方法相比,@TestFactory方法自己不是测试用例,而是测试用例的工厂。 所以,动态测试是工厂的产物。 从技术上讲,@TestFactory方法必须返回Stream,Collection,Iterable,Iterator或DynamicNode实例数组。 DynamicNode的可实例化子类是DynamicContainer和DynamicTest。 DynamicContainer实例由显示名称和动态子节点列表组成,你能够建立任意嵌套的动态节点层次结构。 DynamicTest实例将被延迟执行,从而动态甚至非肯定性地生成测试用例。

任何由@TestFactory方法返回的Stream在调用stream.close()的时候会被正确地关闭,这样咱们就能够安全地使用一个资源,例如:Files.lines()。

跟@Test方法同样,@TestFactory方法不能是private或static的。但它能够声明被ParameterResolvers解析的参数。

DynamicTest是运行时生成的测试用例。它由一个显示名称 和Executable组成。Executable是一个@FunctionalInterface,这意味着动态测试的实现能够是一个lambda表达式 或方法引用

同一个@TestFactory所生成的n个动态测试,@BeforeEach@AfterEach只会在这n个动态测试开始前和结束后各执行一次,不会为每个单独的动态测试都执行。

在JUnit Jupiter5.4.0 中,动态测试必须始终由工厂方法建立;不过,在后续的发行版中,这可能会获得注册工具的补充。

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", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        );

    }

    @TestFactory

    Iterable<DynamicTest> dynamicTestsFromIterable() {

        return Arrays.asList(

            dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        );

    }

    @TestFactory

    Iterator<DynamicTest> dynamicTestsFromIterator() {

        return Arrays.asList(

            dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        ).iterator();

    }

    @TestFactory

    DynamicTest[] dynamicTestsFromArray() {

        return new DynamicTest[] {

            dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        };

    }

    @TestFactory

    Stream<DynamicTest> dynamicTestsFromStream() {

        return Stream.of("racecar", "radar", "mom", "dad")

            .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));

    }

    @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, () -> assertTrue(n % 2 == 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) -> assertTrue(input % 7 != 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", () -> assertNotNull(input)),

                dynamicContainer("properties", Stream.of(

                    dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),

                    dynamicTest("not empty", () -> assertFalse(input.isEmpty()))

                ))

            )));

    }

    @TestFactory

    DynamicNode dynamicNodeSingleTest() {

        return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));

    }

    @TestFactory

    DynamicNode dynamicNodeSingleContainer() {

        return dynamicContainer("palindromes",

            Stream.of("racecar", "radar", "mom", "dad")

                .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))

        ));

    }

 

}

2.18 并行执行测试

默认状况下,JUnit Jupiter测试在单个线程中按顺序运行。要并行运行测试,例如 加速执行,自5.3版本开始做为可选择的功能被加入进来。要启用并行执行,只需将junit.jupiter.execution.parallel.enabled配置参数设置为true,例如 在junit-platform.properties中(请参阅其余选项的 配置参数)。

启用后,JUnit Jupiter引擎将根据提供的 配置 彻底并行地在全部级别的测试树上执行测试,同时观察声明性 同步机制。 请注意,捕获标准输出/错误 功能须要单独开启。

⚠️ 并行测试执行目前是一项实验性功能。 你被邀请尝试并向JUnit团队提供反馈,以便他们能够 改进 并最终推广此功能。

2.18.1 配置

可使用 ParallelExecutionConfigurationStrategy 配置所需并行度和最大池大小等属性。 JUnit平台提供了两种开箱即用的实现:dynamic和fixed。 固然,你也能够实现一个custom的策略。

要选择策略,只需将junit.jupiter.execution.parallel.config.strategy配置参数设置为如下选项之一:

dynamic

根据可用处理器/核心数乘以junit.jupiter.execution.parallel.config.dynamic.factor配置参数(默认为1)计算所需的并行度。

fixed 强制使用junit.jupiter.execution.parallel.config.fixed.parallelism配置参数做为所需的并行度。

custom 容许经过强制junit.jupiter.execution.parallel.config.custom.class配置参数指定自定义 ParallelExecutionConfigurationStrategy 实现,以肯定所需的配置。

若是未设置配置任何策略,则JUnit Jupiter使用因子为1的动态配置策略,即所需的并行度将等于可用处理器/核心的数量。

2.18.2 同步

在org.junit.jupiter.api.parallel包中,JUnit Jupiter提供了两种基于注解的声明性机制,用于在不一样测试中使用共享资源时更改执行模式并容许同步。

若是启用了并行执行,默认状况下会同时执行全部类和方法。你可使用 @Execution 注解更改带注解的元素及其子元素(若是有)的执行模式。有如下两种模式:

SAME_THREAD

强制执行父级使用的同一线程。例如,在测试方法上使用时,测试方法将在与包含测试类的任何@BeforeAll或@AfterAll方法相同的线程中执行。

CONCURRENT

除非存在资源约束要强制在同一线程中执行,不然执行并发。

此外,@ResourceLock 注解容许声明测试类或测试方法使用须要同步访问的特定共享资源,以确保可靠的测试执行。

若是你并行运行下面示例中的测试,你会发现它们很不稳定,即有时经过而其余时间失败。由于它们所读取的资源在写入是存在竞争。

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", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        );

    }

    @TestFactory

    Iterable<DynamicTest> dynamicTestsFromIterable() {

        return Arrays.asList(

            dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        );

    }

    @TestFactory

    Iterator<DynamicTest> dynamicTestsFromIterator() {

        return Arrays.asList(

            dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        ).iterator();

    }

    @TestFactory

    DynamicTest[] dynamicTestsFromArray() {

        return new DynamicTest[] {

            dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),

            dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))

        };

    }

    @TestFactory

    Stream<DynamicTest> dynamicTestsFromStream() {

        return Stream.of("racecar", "radar", "mom", "dad")

            .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));

    }

    @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, () -> assertTrue(n % 2 == 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) -> assertTrue(input % 7 != 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", () -> assertNotNull(input)),

                dynamicContainer("properties", Stream.of(

                    dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),

                    dynamicTest("not empty", () -> assertFalse(input.isEmpty()))

                ))

            )));

    }

    @TestFactory

    DynamicNode dynamicNodeSingleTest() {

        return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));

    }

    @TestFactory

    DynamicNode dynamicNodeSingleContainer() {

        return dynamicContainer("palindromes",

            Stream.of("racecar", "radar", "mom", "dad")

                .map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))

        ));

    }

 

}

当使用该注解声明对共享资源的访问时,JUnit Jupiter引擎会使用此信息来确保不会并行运行冲突的测试。

除了用于惟一标记已使用资源的字符串以外,你还能够指定访问模式。须要对资源进行READ访问的两个测试能够彼此并行运行,除非有其余READ_WRITE访问模式的测试正在运行。

2.19 固有的扩展

JUnit团队鼓励开发者开发扩展,这里介绍一个JUnit已经含有的一个扩展。

@TempDir,实现这个注解具体功能的类网址为https://github.com/junit-team/junit5/blob/r5.4.0/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java

(能够经过实现的代码看到他继承了ParameterResolver,这个又是参数注入的内容,咱们实现自定义注解都要这个样子哦),这个注解的做用是获取当前工做目录。

@Test

    void writeItemsToFile(@TempDir Path tempDir) throws IOException {

        System.out.println(tempDir);

        Path file = tempDir.resolve("/home/user/Coding/JavaCode/SSM/src/test/java/TestJunit5/test.txt");

 

//        new ListWriter(file).write("a", "b", "c");

 

        assertEquals(singletonList("a,b,c"), Files.readAllLines(file));

    }

@TempDir不能放在构造方法上。

下边是一个例子,他还能够标注在静态属性上。

class SharedTempDirectoryDemo {

 

    @TempDir

    static Path sharedTempDir;

 

    @Test

    void writeItemsToFile() throws IOException {

        Path file = sharedTempDir.resolve("test.txt");

 

        new ListWriter(file).write("a", "b", "c");

 

        assertEquals(singletonList("a,b,c"), Files.readAllLines(file));

    }

 

    @Test

    void anotherTestThatUsesTheSameTempDir() {

        // use sharedTempDir

    }

 

}

4 运行测试

参数配置在原用户手册的4.5章节中https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params

能够经过给jvm传参或者在根目录添加junit-platform.properties文件等方法实现JUnit的参数配置。

 

5 扩展模型

这一章节的内容就是如何为Junit添加本身定义的注解,本身定义的参数解析,实现更多的本身须要的功能。

5.2 注册扩展

JUnit Jupiter中的扩展能够经过 @ExtenWith 注解进行声明式注册,或者经过 @RegisterExtension 注解进行编程式注册,再或者经过Java的 ServiceLoader 机制自动注册。

这一章节全部的类都是与 Extension接口有关,能够发现继承它的一些接口是分别对应不一样的扩展,经过实现这些接口来实现咱们须要的功能。

5.2.1 声明式注册扩展

能够经过@ExtendWith传入类来进行扩展。能够传入多个的方法以下。能够看到这个注解的功能应该是能够引入扩展类,而后就能够得到额外的功能,@Random注解是框架所没有的。

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

//@ExtendWith(FooExtension.class)

//@ExtendWith(BarExtension.class)

@ExtendWith(RandomParametersExtension.class)

@Test

void test(@Random int i) {

    // ...

}

5.2.2 编程式扩展

开发人员能够经过编程的 方式来注册扩展,只须要将测试类中的属性字段使用 @RegisterExtension 注解标注便可。

当一个扩展经过 @ExtenWith 声明式注册后,它就只能经过注解配置。相比之下,当经过@RegisterExtension注册扩展时,咱们能够经过编程 的方式来配置扩展 – 例如,将参数传递给扩展的构造函数、静态工厂方法或构建器API。

@RegisterExtension 字段不能为private或null (在评估阶段) ,但能够是static或非静态。

静态字段

若是一个@RegisterExtension字段是static的,该扩展会在那些在测试类中经过@ExtendWith进行注册的扩展以后被注册。这种静态扩展 在扩展API的实现上没有任何限制。所以,经过静态字段注册的扩展可能会实现类级别和实例级别的扩展API,例如BeforeAllCallback、AfterAllCallback和TestInstancePostProcessor,一样还有方法级别的扩展API,例如BeforeEachCallback等等。

在下面的例子中,测试类中的server字段经过使用WebServerExtension所支持的构建器模式以编程的方式进行初始化。已经配置的WebServerExtension将在类级别自动注册为一个扩展 - 例如,要在测试类中全部测试方法运行以前启动服务器,以及在全部测试完成后中止服务器。此外,使用@BeforeAll或@AfterAll标注的静态生命周期方法以及@BeforeEach、@AfterEach和@Test标注的方法能够在须要的时候经过server字段访问该扩展的实例。

一个经过静态字段注册的扩展:

class WebServerDemo {

 

    @RegisterExtension

    static WebServerExtension server = WebServerExtension.builder()

        .enableSecurity(false)

        .build();

 

    @Test

    void getProductList() {

        WebClient webClient = new WebClient();

        String serverUrl = server.getServerUrl();

        // Use WebClient to connect to web server using serverUrl and verify response

        assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());

    }

}

实例字段

若是@RegisterExtension字段是非静态的(例如,一个实例字段),那么该扩展将在测试类实例化以后被注册,而且在每一个已注册的TestInstancePostProcessor被赋予后处理测试实例的机会以后(可能给被标注的字段注入要使用的扩展实例)。所以,若是这样的实例扩展实现了诸如BeforeAllCallback、AfterAllCallback或TestInstancePostProcessor这些类级别或实例级别的扩展API,那么这些API将不会正常执行。默认状况下,实例扩展将在那些经过@ExtendWith在方法级别注册的扩展以后被注册。可是,若是测试类是使用了@TestInstance(Lifecycle.PER_CLASS)配置,实例扩展将在它们以前被注册。

在下面的例子中,经过调用自定义lookUpDocsDir()方法并将结果提供给DocumentationExtension中的静态forPath()工厂方法,从而以编程的方式初始化测试类中的docs字段。配置的DocumentationExtension将在方法级别自动被注册为扩展。另外,@BeforeEach、@AfterEach和@Test方法能够在须要的时候经过docs字段访问扩展的实例。

一个经过静态字段注册的扩展:

class DocumentationDemo {

 

    static Path lookUpDocsDir() {

        // return path to docs dir

    }

 

    @RegisterExtension

    DocumentationExtension docs = DocumentationExtension.forPath(lookUpDocsDir());

 

    @Test

    void generateDocumentation() {

        // use this.docs ...

    }

}

5.2.3 自动注册扩展

除了 声明式扩展注册 和 编程式扩展注册 支持使用注解,JUnit Jupiter还支持经过Java的java.util.ServiceLoader机制进行全局扩展注册,采用这种机制后会自动的检测classpath下的第三方扩展,并自动完成注册。

具体来讲,自定义扩展能够经过在org.junit.jupiter.api.extension.Extension文件中提供其全类名来完成注册,该文件位于其封闭的JAR文件中的/META-INF/services目录下。

启用自动扩展检测

自动检测是一种高级特性,默认状况下它是关闭的。要启用它,只须要在配置文件中将 junit.jupiter.extensions.autodetection.enabled的配置参数 设置为 true便可。该参数能够做为JVM系统属性、或做为一个传递给Launcher的LauncherDiscoveryRequest中的配置参数、再或者经过JUnit Platform配置文件(详情请参阅 配置参数)来提供。

例如,要启用扩展的自动检测,你能够在启动JVM时传入以下系统参数。

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

启用自动检测功能后,经过ServiceLoader机制发现的扩展将在JUnit Jupiter的全局扩展(例如对TestInfo,TestReporter等的支持)以后被添加到扩展注册表中。

5.3 条件测试

ExecutionCondition 定为程序化的条件测试执行定义了ExtensionAPI。

这里为了搞明白怎么自定义扩展,特别是条件扩展,能够参考以下的DisabledOnOs是如何实现的。在注解中经过@ExtendWith()添加判断状况的类,DisabledOnOsCondition是继承了ExecutionCondition用以实现条件判断,根据代码能够看出,在方法中首先尝试获取注解,而后判断注解在不在,在的状况下在判断输入的值(操做系统)是否符合要求。

简单说明一下@Disabled注解的注解判断条件类DisabledCondition类在junit-jupiter-engine中。

 

5.4 测试实例工厂

TestInstanceFactory 为但愿建立测试类实例的Extensions定义了API。

5.5 测试实例后期处理

TestInstancePostProcessor 为但愿发布流程测试实例的Extensions定义了API。

关于具体示例,请查阅 MockitoExtension 和 SpringExtension 的源代码。

https://github.com/mockito/mockito/blob/release/2.x/subprojects/junit-jupiter/src/main/java/org/mockito/junit/jupiter/MockitoExtension.java

https://github.com/spring-projects/spring-framework/blob/master/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java

5.6 参数解析

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

为了实现传入自定义的参数,咱们要实现对应的参数解析器。能够参照TestInfoParameterResolver这个类来写参数解析器,这个解析器解析的是在以前章节提到过的三种Junit自带的参数解析器,对应解析TestInfo类,能够在测试方法中传入TestInfo参数获取对应测试的相关信息。

5.7 测试结果处理

TestWatcher提供API以实现扩展用以处理测试方法的结果。经过看这个类的注释,大概知道这个接口用来提供测试方法被跳过或者被执行等结果进行报告。

其中四种方法分别对应

testDisabled: 被@Disabled注释的方法跳事后

testSuccessful: 成功测试

testAborted:测试终止

testFailed: 测试失败

5.8 测试生命周期回调

下列接口定义了用于在测试执行生命周期的不一样阶段来扩展测试的API。关于每一个接口的详细信息,能够参考后续章节的示例,也能够查阅 org.junit.jupiter.api.extension 包中的Javadoc。

BeforeAllCallback

BeforeEachCallback

BeforeTestExecutionCallback

AfterTestExecutionCallback

AfterEachCallback

AfterAllCallback
扩展开发人员能够选择在单个扩展中实现任意数量的上述接口。

BeforeTestExecutionCallback 和 AfterTestExecutionCallback 分别为Extensions定义了添加行为的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,因此,测试将在执行时应用这个计时器。

一个使用示例TimingExtension的测试类

import org.junit.jupiter.api.Test;

import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(TimingExtension.class)

class TimingExtensionTests {

    @Test

    void sleep20ms() throws Exception {

        Thread.sleep(20);

    }

    @Test

    void sleep50ms() throws Exception {

        Thread.sleep(50);

    }

}

测试结果以下图所示。

 

5.9 异常处理

TestExecutionExceptionHandler 为Extensions定义了异常处理的API,从而能够处理在执行测试时抛出的异常。

5.10 为测试模板提供调用上下文

当至少有一个 TestTemplateInvocationContextProvider 被注册时,标注了 @TestTemplate 的方法才能被执行。每一个这样的provider负责提供一个 TestTemplateInvocationContext 实例的Stream。每一个上下文均可以指定一个自定义的显示名称和一个额外的扩展名列表,这些扩展名仅用于下一次调用 @TestTemplate 方法。

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

public class MyTestTemplateInvocationContextProviderTest {

    final List<String> fruits = Arrays.asList("apple", "banana", "lemon");

    //这里的做用是想将字符串对象传入String fruit

    //所谓测试模板确定是要能传入一系列的参数,因此在具体实现中是要返回一个stream对象

    @TestTemplate

    @ExtendWith(MyTestTemplateInvocationContextProvider.class)

    void testTemplate(String fruit) {

        assertTrue(fruits.contains(fruit));

    }

}

 

//TestTemplateInvocationContextProvider继承这个接口以实现测试模板

public class MyTestTemplateInvocationContextProvider

        implements TestTemplateInvocationContextProvider {

    //是否支持测试模板,true为开启支持

    @Override

    public boolean supportsTestTemplate(ExtensionContext context) {

        return true;

    }

   

    @Override

    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(

            ExtensionContext context) {

 

        return Stream.of(invocationContext("apple"), invocationContext("banana"));

    }

    //这里选择含两个元素的stream流,须要将对应的String类型转换为TestTemplateInvocationContext类型

    private TestTemplateInvocationContext invocationContext(String parameter) {

        return new TestTemplateInvocationContext() {

            //这里将传入的String参数设置为测试中的DisplayName

            @Override

            public String getDisplayName(int invocationIndex) {

                return parameter;

            }

           

            @Override

            public List<Extension> getAdditionalExtensions() {

                return Collections.singletonList(new ParameterResolver() {

                    //判断是不是//判断是不是String类型String类型

                    @Override

                    public boolean supportsParameter(ParameterContext parameterContext,

                                                     ExtensionContext extensionContext) {

                        return parameterContext.getParameter().getType().equals(String.class);

                    }

                    //把String类型参数直接返回,咱们须要解析的就是String类型,若是是别的类型确定要涉及转换

                    @Override

                    public Object resolveParameter(ParameterContext parameterContext,

                                                   ExtensionContext extensionContext) {

                        return parameter;

                    }

                });

            }

        };

    }

}

以前讲过测试模板的例子,@RepeatedTest和@ParameterizedTest就是两个典型,咱们若是要写本身的测试模板能够参照这两个例子的实现,简单看下。

能够看到@RepeatedTest的确是包含了@TestTemplate。

 

而后具体的注解处理类在junit-platform-engine中的RepeatedTestExtension类中,能够看到熟悉的要重写的方法,第一个supportsTestTemplate方法中的逻辑是“方法是否被RepeatedTest注解标注”,第二个provideTestTemplateInvocationContexts方法中的逻辑是“返回IntStream流,由于@RepeatedTest的注解里边参数是传的int类型”。

 

5.11 在扩展中保持状态

一般,扩展只实例化一次。随之而来的相关问题是:开发者如何可以在两次调用之间保持扩展的状态?ExtensionContext API提供了一个Store用来解决这一问题(参见测试生命周期回调那个例子)。扩展能够将值放入Store中供之后检索。请参阅 TimingExtension 了解如何使用具备方法级做用域的Store。要注意,在测试执行期间,被存储在一个ExtensionContext中的值在周围其余的ExtensionContext中是不可用的。因为ExtensionContexts多是嵌套的,所以内部上下文的范围也可能受到限制。请参阅相应的Javadoc来了解有关经过 Store 存储和检索值的方法的详细信息。

5.12 在扩展中支持的实用程序

junit-platform-commons公开了一个名为 的包,它包含了用于处理注解、类、反射和类路径扫描任务且正在维护中的实用工具方法。TestEngine和Extension开发人员(authors)应该被鼓励去使用这些方法,以便与JUnit Platform的行为保持一致。

5.12.1 注解支持

AnnotationSupport提供对注解元素(例如包、注解、类、接口、构造函数、方法和字段)进行操做的静态实用工具方法。这些方法包括检查元素是否使用特定注释进行注解或元注解,搜索特定注解以及如何在类或界面中查找注解的方法和字段。其中一些方法搜索已实现的接口和类层次结构以查找注解。有关更多详细信息,请参阅JavaDoc的 AnnotationSupport

5.12.2. 类支持

ClassSupport提供静态工具方法来处理类(即java.lang.Class的实例)。有关详细信息,请参阅JavaDoc的 ClassSupport

5.12.3 反射支持

ReflectionSupport提供了静态实用工具方法,以加强标准的JDK反射和类加载机制。这些方法包括扫描类路径以搜索匹配了指定谓词的类,加载和建立类的新实例以及查找和调用方法。其中一些方法能够遍历类层次结构以找到匹配的方法。有关更多详细信息,请参阅JavaDoc的 ReflectionSupport

5.12.4. Modifier Support

ModifierSupport provides static utility methods for working with member and class modifiers — for example, to determine if a member is declared as public, private, abstract, static, etc. Consult the Javadoc for ModifierSupport for further details.

5.13 用户代码和扩展的相对执行顺序

5.13.1 用户和扩展代码

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

 

用户代码和扩展代码

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

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

步骤

接口/注解

描述

1

接口org.junit.jupiter.api.extension.BeforeAllCallback

执行全部容器测试以前执行的扩展代码

2

注解org.junit.jupiter.api.BeforeAll

执行全部容器测试以前执行的用户代码

3

接口org.junit.jupiter.api.extension.BeforeEachCallback

每一个测试执行以前执行的扩展代码

4

注解org.junit.jupiter.api.BeforeEach

每一个测试执行以前执行的用户代码

5

接口org.junit.jupiter.api.extension.BeforeTestExecutionCallback

测试执行以前当即执行的扩展代码

6

注解org.junit.jupiter.api.Test

真实测试方法的用户代码

7

接口org.junit.jupiter.api.extension.TestExecutionExceptionHandler

用于处理测试期间抛出的异常的扩展代码

8

接口org.junit.jupiter.api.extension.AfterTestExecutionCallback

测试执行后当即执行的扩展代码

9

注解org.junit.jupiter.api.AfterEach

每一个执行测试以后执行的用户代码

10

接口org.junit.jupiter.api.extension.AfterEachCallback

每一个执行测试以后执行的扩展代码

11

注解org.junit.jupiter.api.AfterAll

执行全部容器测试以后执行的用户代码

12

接口org.junit.jupiter.api.extension.AfterAllCallback

执行全部容器测试以后执行的扩展代码

在最简单的状况下,只有实际的测试方法被执行(步骤6); 全部其余步骤都是可选的,具体包含的步骤将取决因而否存在用户代码或对相应生命周期回调的扩展支持。有关各类生命周期回调的更多详细信息,请参阅每一个注解和扩展各自的JavaDoc。

5.13.2 回调的包装行为

对应https://junit.org/junit5/docs/current/user-guide/#extensions-execution-order-wrapping-behavior 章节,大体就是讲不一样自定义扩展被加入以后的执行顺序和涉及类继承关系时的测试执行顺序。