赞
踩
JUnit 5 与以前版本的 JUnit 不同,拆分成由三个不同子项目的几个不同模块组成。
<!--junit5--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-runner</artifactId> <version>1.8.2</version> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>1.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-console-standalone</artifactId> <version>1.8.2</version> <scope>test</scope> </dependency>
如果要从命令行运行测试,可以配置一个maven surefire插件:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
顶级类
、静态成员类
、或使用@Nested注解的包含一个或多个测试方法的内部类
。测试类不能是抽象的,必须有单一的构造方法。构造方法必须不带参数,或者所带参数能够通过依赖注入在运行时动态解析。作为可见性的最低要求,测试类允许是包私有的,不再像JUnit4.x那样要求测试类是共有类。org.junit.jupiter.api
包中org.junit.jupiter.api.Assertions
类中
org.junit.jupiter.api.Assumptions
类中@BeforeEachand和@AfterEach
代替@BeforeAlland和@AfterAll
代替@Disabled
或其他内置执行条件
之一代替@Tag
代替使用@ExtendWith
取代@ExtendWith和@RegisterExtension
取代Assertions.assertThrows(…)
代替使用public class LifeCycleTest { @BeforeEach public void beforeEach() { System.out.println("BeforeEach"); } @AfterEach public void afterEach() { System.out.println("AfterEach"); } @BeforeAll public static void beforeAll() { System.out.println("BeforeAll"); } @AfterAll public static void afterAll() { System.out.println("AfterAll"); } @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } }
BeforeAll
BeforeEach
test1
AfterEach
BeforeEach
test2
AfterEach
AfterAll
JUnit在调用每个@Test标注的方法之前创建测试类的一个新实例,以确保测试方法的独立性,并防止测试代码中出现意想不到的副作用。另外,测试得到的结果必须与运行顺序无关。因为每个测试方法都在测试类的一个新实例上运行所以不能跨测试方法重用实例变量值。为要运行的每个测试方法创建测试类的一个实例,这是JUnit5和之前所有版本的默认行为。
如果用@TestInstance(Lifecycle.PER_CLASS)标注测试类,JUnit5将在同一个测试类实例上运行所有测试方法。使用该注解,可以为每个测试类创建一个新的测试实例。
生命周期模式有两个:
@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class InstanceTest { private int num = 0; @Test public void test1() { num++; System.out.println("test1 num:" + num); } @Test public void test2() { num++; System.out.println("test2 num:" + num); } }
test1 num:1
test2 num:2
测试类或方法的显示名称是根据以下优先规则确定的:
@DisplayName注解可用于类和测试方法。该注解可以让Tested Data Systerms公司的开发人员为一个测试类或测试方法指定显示名称。通常,该注解用于IDE和构建工具的测试报告中。@DisplayName注解的字符串参数可以包含空格、特殊字符,甚至是表情符号。
@DisplayName("测试类")
public class DisplayNameTest {
@Test
@DisplayName("第一个测试")
public void test1() {
System.out.println("test1");
}
@Test
@DisplayName("第二个测试")
public void test2() {
System.out.println("test2");
}
}
JUnit Jupiter 支持可以通过 @DisplayNameGeneration注释进行配置的自定义显示名称生成器。通过注释提供的值@DisplayName始终优先于DisplayNameGenerator。
默认的生成器:
public class DisplayNameGeneratorTest { @Nested @DisplayNameGeneration(DisplayNameGenerator.Standard.class) class TestA { @Test void test_A_test() { System.out.println("testA.test"); } } @Nested @DisplayNameGeneration(DisplayNameGenerator.Simple.class) class TestB { @ParameterizedTest(name = "TestB.test, arg is {0}") @ValueSource(ints = {1, 2}) void test_B_test(int arg) { System.out.println("testB.test"); } } @Nested @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class TestC { @Test void test_C_test() { System.out.println("testC.test"); } } @Nested @IndicativeSentencesGeneration(separator = "->", generator = DisplayNameGenerator.ReplaceUnderscores.class) class TestD { @Test void test_D_test() { System.out.println("testD.test"); } } }
可以通过配置文件指定默认名称生成器:
junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
public class OsTest {
@Test
@EnabledOnOs({OS.MAC})
public void test1() {
System.out.println("test1");
}
@Test
@DisabledOnOs(OS.WINDOWS)
public void test2() {
System.out.println("test2");
}
}
test1
test2
public class JreTest { @Test @EnabledOnJre({JRE.JAVA_8}) public void test1() { System.out.println("test1"); } @Test @DisabledOnJre({JRE.JAVA_12}) public void test2() { System.out.println("test2"); } @Test @EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_9) public void test3() { System.out.println("test3"); } @Test @DisabledForJreRange(min = JRE.JAVA_9, max = JRE.JAVA_12) public void test4() { System.out.println("test4"); } }
test1
test2
test3
test4
public class SysTest { @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") public void test1() { System.out.println(System.getProperty("os.arch")); } @Test @DisabledIfSystemProperty(named = "os.name", matches = "linux") public void test2() { System.out.println(System.getProperty("os.name")); } @Test @EnabledIfSystemProperties({ @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*"), @EnabledIfSystemProperty(named = "os.name", matches = "Mac.*") }) public void test3() { System.out.println("test3"); } @Test @DisabledIfSystemProperties({ @DisabledIfSystemProperty(named = "os.arch", matches = ".*32.*"), @DisabledIfSystemProperty(named = "os.name", matches = "linux") }) public void test4() { System.out.println("test4"); } }
x86_64
Mac OS X
test3
test4
public class EnvTest { @Test @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*home") public void test1() { System.out.println("test1"); } @Test @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*home") public void test2() { System.out.println("test2"); } @Test @EnabledIfEnvironmentVariables({ @EnabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*home"), @EnabledIfEnvironmentVariable(named = "MAVEN_HOME", matches = ".*home") }) public void test3() { System.out.println("test3"); } @Test @DisabledIfEnvironmentVariables({ @DisabledIfEnvironmentVariable(named = "JAVA_HOME", matches = ".*home"), @DisabledIfEnvironmentVariable(named = "MAVEN_HOME", matches = ".*home") }) public void test4() { System.out.println("test4"); } }
Environment variable [JAVA_HOME] with value [/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home] does not match regular expression [.*home]
test2
Environment variable [JAVA_HOME] with value [/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home] does not match regular expression [.*home]
test4
public class IfTest { @Test @EnabledIf("enabledCondition") public void test1() { System.out.println("test1"); } @Test @DisabledIf("demo.IfTest$MyCondition#disabledCondition") public void test2() { System.out.println("test2"); } boolean enabledCondition() { return true; } static class MyCondition { static boolean disabledCondition() { return false; } } }
test1
test2
在多种情况下,需要使用条件方法static:
默认情况下,测试类和方法将使用确定性但故意不明显的算法进行排序。这确保了测试套件的后续运行以相同的顺序执行测试类和测试方法,从而允许可重复的构建。
@TestMethodOrder注解通过指定MethodOrderer实现定义方法的执行顺序。
内置的MethodOrderer如下:
public class MethodOrderTest { @Nested @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class A { @Test @Order(10) public void test1() { System.out.println("A.test1"); } @Test @Order(100) public void test2() { System.out.println("A.test2"); } } @Nested @TestMethodOrder(MethodOrderer.DisplayName.class) class B { @Test @DisplayName("test1") public void test1() { System.out.println("B.test1"); } @Test @DisplayName("test2") public void test2() { System.out.println("B.test2"); } } }
B.test1
B.test2
A.test1
A.test2
可以使用junit.jupiter.testmethod.order.default 配置参数MethodOrderer来指定默认情况下要使用的完全限定类名 。就像通过@TestMethodOrder注释配置的排序者一样,提供的类必须实现该 MethodOrderer接口。默认排序程序将用于所有测试,除非 @TestMethodOrder注释出现在封闭的测试类或测试接口上。
junit.jupiter.testmethod.order.default = org.junit.jupiter.api.MethodOrderer$OrderAnnotation
@TestClassOrder注解通过指定ClassOrderer实现定义类的执行顺序。
内置的ClassOrderer实现类如下:
@TestClassOrder(ClassOrderer.OrderAnnotation.class) public class ClassOrderTest { @Nested @Order(1000) class A { @Test public void test() { System.out.println("A.test"); } } @Nested @Order(100) class B { @Test public void test() { System.out.println("B.test"); } } @Nested @Order(10) class C { @Test public void test() { System.out.println("C.test"); } } }
C.test
B.test
A.test
@Disabled注解可用于测试类和方法,表示禁用测试类或测试方法不予以运行。开发人员用这个注解给出禁用一个测试的理由,以便团队的其他成员确切地知道为什么要这么做。如果该注解用在一个类上,将禁用该测试类的所有方法。此外,当开发人员在IDE运行测试时,被禁用的测试及禁用原因在不同的控制台上显示的内容也有所不同。
禁用整个测试类:
@Disabled("禁用整个测试类")
public class DisabledClassTest {
@Test
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
}
禁用方法:
public class DisabledMethodsTest {
@Test
@Disabled("禁用test1")
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
}
当我们编写的类和代码逐渐增多,随之而来的需要测试的对应测试类也会越来越多。为了解决测试类数量爆炸的问题,JUnit 5提供了@Nested 注解,能够以静态内部成员类的形式对测试用例类进行逻辑分组。
并且每个静态内部类都可以有自己的生命周期方法, 这些方法将按从外到内层次顺序执行。 此外,嵌套的类也可以用@DisplayName 标记,这样我们就可以使用正确的测试名称。
@DisplayName("嵌套测试") public class NestClassTest { @BeforeEach public void init() { System.out.println("before each"); } @Nested @DisplayName("第一个内嵌测试类") class FirstNestTest { @Test void test() { System.out.println("first class test"); } } @Nested @DisplayName("第二个内嵌测试类") class SecondNestTest { @Test void test() { System.out.println("second class test"); } } }
before each
second class test
before each
first class test
可以在类和测试方法上是使用@Tag注解标记,安后可以利用这些标记过滤测试的发现和运行。
@Tag("testOne") public class TagTestOne { @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } } @Tag("testTwo") public class TagTestTwo { @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } }
激活这些标记测试类的方法有两种:
一种是在pom.xml配置文件上配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<groups>testOne</groups>
<excludedGroups>testTwo</excludedGroups>
</configuration>
</plugin>
</plugins>
</build>
另一种方法是子IDEA中选择Run>Edit Configurations>Tags(JUnit5)作为测试类型,创建一个配置:
JUnit5允许使用@RepeatedTest注解指定一个测试重复运行的次数,该注解需要指定重复的次数作为参数。当从一次测试运行到另一次测试时,测试条件可能改变,此特性将非常有用。例如,一些影响成功测试的数据可能在同一测试的两次运行之间发生改变,而对数据的意外修改将产生一个需要修复的错误。
可以用@RepeatedTest注解的name属性为每次重复测试配置自定义显示名称,该注解支持以下占位符:
{displayName}
:待@Repeatedtest注解的方法的显示名称{currentRepetition}
:当前重复次数{totalRepetitions}
:总重复次数public class RepeatedTestsTest { private static Set<Integer> integerSet = new HashSet<>(); private static List<Integer> integerList = new ArrayList<>(); /** * 第一个测试重复5次,每次重复输出显示名称、当前重复次数和总重复次数 */ @RepeatedTest(value = 5, name = "{displayName} - repetition {currentRepetition}/{totalRepetitions}") @DisplayName("test1") public void test1() { System.out.println("test1"); } /** * 第二个次数重复5次,每次重复都会显示列表中的元素数量(当前重复次数),并检查集合是否总是只有一个元素 * 每次重复第二个测试时,重复次数都会显示出来,因为重复次数已经被注入RepetitionInfo参数中 */ @RepeatedTest(value = 5, name = "the list contains {currentRepetition} elements(s), the set contains 1 element") public void test2(TestReporter testReporter, RepetitionInfo repetitionInfo) { integerSet.add(1); integerList.add(repetitionInfo.getCurrentRepetition()); testReporter.publishEntry("Repetition number", String.valueOf(repetitionInfo.getCurrentRepetition())); assertEquals(1, integerSet.size()); assertEquals(repetitionInfo.getCurrentRepetition(), integerList.size()); } }
test1
test1
test1
test1
test1
timestamp = 2024-01-11T01:00:48.307, Repetition number = 1
timestamp = 2024-01-11T01:00:48.316, Repetition number = 2
timestamp = 2024-01-11T01:00:48.319, Repetition number = 3
timestamp = 2024-01-11T01:00:48.322, Repetition number = 4
timestamp = 2024-01-11T01:00:48.328, Repetition number = 5
参数化测试允许使用不同的参数多次运行一个测试。这样做的最大好处是,可以编写测试,然后使用参数来运行测试。这些参数用于检查各种输入数据。参数化测试方法使用@ParameterizedTest注解。必须至少声明一个为每次调用提供参数的源,然后将参数传递给测试方法。
@ParameterizedTest注解包含以下属性:
{displayName}
:@DisplayName注解指定的显示名称{index}
:方法的当前调用次数(1开始){arguments}
:方法的当前调用的完整逗号分隔参数列表{argumentsWithNames}
:方法的当前调用的完整、comm-aseparated命名参数列表@ValueSource注解需要指定一个字面值数组。在运行时,此数组为参数化测试的每次调用提供一个参数
。支持 Java 的八大基本类型和字符串及Class,使用时赋值给注解上对应类型属性。
@ValueSource包含如下属性,用于指定各个类型的参数:
shorts
bytes
ints
longs
floats
doubles
chars
booleans
strings
classes
public class ValueSourceTest {
@DisplayName("测试1")
@ParameterizedTest(name = "{displayName}-{index}-{arguments}-{argumentsWithNames}")
@ValueSource(ints = {10, 20, 30})
public void test1(int num) {
System.out.println(num);
}
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
public void test2(String name) {
System.out.println(name);
}
}
10
20
30
one
two
three
@EnumSource注解让我们能够使用enum实例,并提供了一个可选的names参数,以指定必须使用或排除哪些实例。默认情况下,使用所有的enum实例。
@EnumSource包含如下属性:
INCLUDE
:仅选择那些通过提供名称的枚举常量EXCLUDE
:排除那些通过提供名称的枚举常量MATCH_ALL
:仅选择那些名称与通过属性提供的所有模式匹配的枚举常量。MATCH_ANY
:仅选择那些名称与通过属性提供的任何模式匹配的枚举常量。public class EnumSourceTest { @ParameterizedTest @EnumSource(value = TestEnum.class) public void test1(TestEnum testEnum) { System.out.println(testEnum.value()); } @ParameterizedTest @EnumSource(value = TestEnum.class, mode = EnumSource.Mode.INCLUDE, names = {"ENUM_ONE"}) public void test2(TestEnum testEnum) { System.out.println(testEnum.value()); } @ParameterizedTest @EnumSource(value = TestEnum.class, mode = EnumSource.Mode.EXCLUDE, names = {"ENUM_ONE"}) public void test3(TestEnum testEnum) { System.out.println(testEnum.value()); } @ParameterizedTest @EnumSource(value = TestEnum.class, mode = EnumSource.Mode.MATCH_ALL, names = {"ENUM.*"}) public void test4(TestEnum testEnum) { System.out.println(testEnum.value()); } enum TestEnum { ENUM_ONE("one"), ENUM_TWO("tow"), ENUM_THREE("three"); private final String word; TestEnum(String word) { this.word = word; } public String value() { return word; } } }
one
tow
three
one
tow
three
one
tow
three
@CsvFileSource允许从类路径中使用CSV文件。参数化测试对CSV文件的每一行运行一次。
@CsvFileSource包含一下属性:
\'
)用于带引号的字符串的引用的字符public class CsvFileSourceTest {
@ParameterizedTest
@CsvFileSource(files = "param.csv")
public void test1(int num, String name) {
System.out.println(num + ":" + name);
}
@ParameterizedTest
@CsvFileSource(files = "resource.cvs", delimiterString = "*")
public void test2(String name, int age, double height) {
System.out.println(name + ":" + age + ":" + height);
}
}
param.cvs
10,tom
20,jerry
30,mike
resource.cvs
tom*18*1.82
jerry*22*1.77
mike*25*1.88
10:tom
20:jerry
30:mike
tom:18:1.82
jerry:22:1.77
mike:25:1.88
@CsvSource提供csv格式的多参数源,@CsvSource属性如下:
public class CvsSourceTest { @ParameterizedTest @CsvSource(value = { "tom,18,1.82", "jerry,22,1.77", "mike,33,1.69" }) public void test1(String name, int age, double height) { System.out.println(name + ":" + age + ":" + height); } @ParameterizedTest @CsvSource(delimiterString = "*", useHeadersInDisplayName = true, quoteCharacter = '"', textBlock = """ name* age* height tom* 18* 1.82 jerry* 22* 1.77 mike* 33* 1.69 """) public void test2(String name, int age, double height) { System.out.println(name + ":" + age + ":" + height); } }
@TestInstance(Lifecycle.PER_CLASS)
测试方法参数对应的工厂方法返回值:
@ParameterizedTest 方法 | 工厂方法 |
---|---|
void test(int) | static int[] factory() |
void test(int) | static IntStream factory() |
void test(String) | static String[] factory() |
void test(String) | static List<String> factory() |
void test(String) | static Stream<String> factory() |
void test(String, String) | static String[][] factory() |
void test(String, int) | static Object[][] factory() |
void test(String, int) | static Stream<Object[]> factory() |
void test(String, int) | static Stream<Arguments> factory() |
void test(int[]) | static int[][] factory() |
void test(int[]) | static Stream<int[]> factory() |
void test(int[][]) | static Stream<int[][]> factory() |
void test(Object[][]) | static Stream<Object[][]> factory() |
public class MethodSourceTest { /** * 指定参数方法名 */ @MethodSource("method1") @ParameterizedTest public void test1(String name, int age, double height) { System.out.println(name + ":" + age + ":" + height); } static Stream<Object[]> method1() { return Stream.of( new Object[]{"tom", 18, 1.88}, new Object[]{"jerry", 20, 1.77}, new Object[]{"mike", 22, 1.65} ); } /** * 不指定参数方法名,默认查找同名方法 */ @MethodSource @ParameterizedTest public void test2(String name, String city) { System.out.println(name + ":" + city); } static String[][] test2() { return new String[][] { {"tom", "beijing"}, {"jerry", "shanghai"}, {"mike", "chengdu"} }; } /** * 多个参数也可以返回Arguments实例的集合 */ @MethodSource("method3") @ParameterizedTest public void test3(String name, int age, double height) { System.out.println(name + ":" + age + ":" + height); } static Stream<Arguments> method3() { return Stream.of( Arguments.of("tom", 18, 1.88), Arguments.of("jerry", 20, 1.77), Arguments.of("mike", 22, 1.65) ); } }
tom:18:1.88
jerry:20:1.77
mike:22:1.65
tom:beijing
jerry:shanghai
mike:chengdu
tom:18:1.88
jerry:20:1.77
mike:22:1.65
@ArgumentsSource 可以用来指定一个自定义且能够复用的ArgumentsProvider。
public class ArgumentsSourceTest { @ParameterizedTest @ArgumentsSource(MyArgumentsProvider.class) public void test(String a, String b, int c) { System.out.println(a + ":" + b + ":" + c); } static class MyArgumentsProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception { return Stream.of( Arguments.of("a", "b", 1), Arguments.of("c", "d", 2) ); } } }
a:b:1
c:d:2
为了检查极端情况并验证我们的软件在提供错误输入null时的正确行为,向我们的参数化测试提供空值可能会很有用。以下注释用作null接受单个参数的参数化测试的源和空值。
public class NullTest { @ParameterizedTest @NullSource public void test1(String str) { Assertions.assertNull(str); } @ParameterizedTest @EmptySource public void test2(int[] arr) { Assertions.assertEquals(0, arr.length); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = { " ", " ", "\t", "\n" }) public void test3(String text) { Assertions.assertTrue(text == null || text.trim().isEmpty()); } }
JUnit5中引入了一种新的动态编程模型,可以在运行时生成测试。编写一个工厂方法,在运行时该方法会创建一系列要运行的测试。这样的工厂方法必须使用@TestFactory注解。使用@TestFactory注解的方法不是常规测试,而是一个生成测试的工厂。
使用@TestFactory标注的方法必须返回以下内容之一:
与用@Test标注方法的要求一样,作为可见性的最低要求,@TestFactory标注的方法允许是包私有的,但不能是私有的或静态的,还可以声明由一个ParameterResolver}解析的参数。
DynamicTest是在运行时生成的测试用例,由一个显示名称和一个Executable组成。Executable是Java8的一个函数式接口。动态测试的实现可以作为Lambda表达式或方法引用来提供。
动态测试与用@Test标注的标准测试有不同的生命周期。标注了@BeforeEach和@AfterEach的方法是针对@TestFactory标注的方法来运行的,而不是针对每个动态测试。除了这些方法,没有针对单个动态测试的生命
周期回调。@BeforeA和@AfterAll的行为保持不变,即在所有测试运行之前和所有测试运行之后运行。
public class DynamicTestsTest { //所有测试开始前运行一次 @BeforeAll static void beforeAll() { System.out.println("beforeAll"); } //所有测试结束后运行一次 @AfterAll static void afterAll() { System.out.println("afterAll"); } //在@TestFactory方法之前运行一次 @BeforeEach public void beforeEach() { System.out.println("beforeEach"); } //在@TestFactory方法之后运行一次 @AfterEach public void afterEach() { System.out.println("afterEach"); } @TestFactory Iterator<DynamicTest> testfactory() { return Arrays.asList( DynamicTest.dynamicTest("test1", () -> { System.out.println("test1"); }), DynamicTest.dynamicTest("test2", () -> { System.out.println("test2"); }), DynamicTest.dynamicTest("test3", () -> { System.out.println("test3"); }) ).iterator(); } }
beforeAll
beforeEach
afterEach
test1
test2
test3
afterAll
通过selectXXX来创建套件,通过包、类、和方法三个维度来选择需要执行的测试用例。所有条件是且的关系,求的是交集。
package pers.zhang.suite; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; public class TestA { @Test @Tag("A1") public void test1() { System.out.println("TestA.test1"); } @Test public void test2() { System.out.println("TestA.test2"); } }
package pers.zhang.suite; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; public class TestB { @Test public void test1() { System.out.println("TestB.test1"); } @Test @Tag("B2") public void test2() { System.out.println("TestB.test2"); } }
// 1 @RunWith+@SelectPackages+@IncludePackages+@ExcludePackages
// @SelectPackages 选择需要执行的测试包
// @IncludePackages 只执行选中的测试包 需要与@SelectPackages配合使用
// @ExcludePackages过滤掉所选择的测试包
@RunWith(JUnitPlatform.class)
@SelectPackages({"pers.zhang"})
@IncludePackages({"pers.zhang.suite"})
@ExcludePackages({"pers.zhang.junit5"})
public class Suite1 {
}
TestB.test1
TestB.test2
TestA.test1
TestA.test2
// 2 @RunWith+@SelectClasses
// @SelectClasses选中需要执行的测试类
@RunWith(JUnitPlatform.class)
@SelectClasses({pers.zhang.suite.TestA.class})
public class Suite2 {
}
TestA.test1
TestA.test2
// 3 @RunWith+@SelectPackages+@IncludeClassNamePatterns+@ExcludeClassNamePatterns
// @SelectPackages 选中需要执行的测试包
// @IncludeClassNamePatterns 只执行测试包中对应的测试类 与@SelectPackages配合使用
// @ExcludeClassNamePatterns 过滤掉测试包中的测试类
@RunWith(JUnitPlatform.class)
@SelectPackages({"pers.zhang.suite"})
@IncludeClassNamePatterns("pers.zhang.suite.TestB")
@ExcludeClassNamePatterns("pers.zhang.suite.TestA")
public class Suite3 {
}
TestB.test1
TestB.test2
// 4 @RunWith+@SelectPackages+@IncludeTags+@ExcludeTags
// @SelectPackages选中需要执行的测试包
// @IncludeTags 执行包含标签的用例 ,标签打在测试用例前
// @ExcludeTags 过滤掉包含标签的用例
@RunWith(JUnitPlatform.class)
@SelectPackages({ "pers.zhang.suite"})
@IncludeTags({ "A1"})
@ExcludeTags({ "B2"})
public class Suite4 {
}
TestA.test1
@Timeout注释允许声明如果测试、测试工厂、测试模板或生命周期方法的执行时间超过给定的持续时间,则该测试、测试工厂、测试模板或生命周期方法应该失败。持续时间的时间单位默认为秒,但可以配置。
@Timeout注解包含两个属性:
public class TimeoutTest { @Test @Timeout(5) public void test1() { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } } @Test @Timeout(value = 10, unit = TimeUnit.MILLISECONDS) public void test2() { try { Thread.sleep(8); } catch (InterruptedException e) { e.printStackTrace(); } } }
要将相同的超时应用于测试类及其所有@Nested 类中的所有测试方法,您可以@Timeout在类级别声明注释。然后,它将应用于该类及其类中的所有测试、测试工厂和测试模板方法 ,除非被特定方法或类上的注释 @Nested覆盖。请注意,在类级别声明的注释不适用于生命周期方法。
@Timeout在方法上声明@TestFactory会检查工厂方法是否在指定的持续时间内返回,但不会验证 DynamicTest工厂生成的每个单独的执行时间。
junit5 从5.3开始支持多线程并发测试,需要在配置文件junit-platform.properties
配置相关信息,配合@RepeatedTest注解使用。
@Execution只有一个属性,取值为ExecutionMode枚举类:
test/resources 目录下,增加 junit-platform.properties 文件,内容如下:
# 是否允许并行执行true/false
junit.jupiter.execution.parallel.enabled=true
#是否支持方法级别多线程same_thread/concurrent
junit.jupiter.execution.parallel.mode.default=concurrent
#是否支持类级别多线程same_thread/concurrent
junit.jupiter.execution.parallel.mode.classes.default=concurrent
#线程池策略
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=5
public class ConcurrentTest {
@RepeatedTest(10)
@Execution(ExecutionMode.CONCURRENT)
public void test() {
System.out.println(Thread.currentThread().getName());
}
}
ForkJoinPool-1-worker-5
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-1
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-2
ForkJoinPool-1-worker-4
ForkJoinPool-1-worker-3
ForkJoinPool-1-worker-5
ForkJoinPool-1-worker-1
除了使用@Execution注解来控制执行模式之外,JUnit Jupiter 还提供了另一种基于注解的声明式同步机制。该 @ResourceLock
注释允许您声明测试类或方法使用需要同步访问的特定共享资源以确保可靠的测试执行。
共享资源由唯一的名称标识,该名称是String. 该名称可以是用户定义的或org.junit.jupiter.api.parallel.Resources
中的预定义常量之一:
除了String唯一标识共享资源之外,还可以指定访问模式:
@Execution(ExecutionMode.CONCURRENT) public class ResourceLockTest { private static int num = 20; @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE) @RepeatedTest(10) public void test1() { num--; System.out.println("test1->" + Thread.currentThread().getName() + " num=" + num); } @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ_WRITE) @RepeatedTest(10) public void test2() { num--; System.out.println("test2->" + Thread.currentThread().getName() + " num=" + num); } }
test1->ForkJoinPool-1-worker-2 num=19 test1->ForkJoinPool-1-worker-2 num=18 test1->ForkJoinPool-1-worker-2 num=17 test1->ForkJoinPool-1-worker-2 num=16 test1->ForkJoinPool-1-worker-2 num=15 test1->ForkJoinPool-1-worker-2 num=14 test1->ForkJoinPool-1-worker-2 num=13 test1->ForkJoinPool-1-worker-2 num=12 test1->ForkJoinPool-1-worker-2 num=11 test1->ForkJoinPool-1-worker-2 num=10 test2->ForkJoinPool-1-worker-3 num=9 test2->ForkJoinPool-1-worker-3 num=8 test2->ForkJoinPool-1-worker-3 num=7 test2->ForkJoinPool-1-worker-3 num=6 test2->ForkJoinPool-1-worker-3 num=5 test2->ForkJoinPool-1-worker-3 num=4 test2->ForkJoinPool-1-worker-3 num=3 test2->ForkJoinPool-1-worker-3 num=2 test2->ForkJoinPool-1-worker-3 num=1 test2->ForkJoinPool-1-worker-3 num=0
如果您的大多数测试类可以并行运行而无需任何同步,但您有一些测试类需要隔离运行,则可以使用注释标记后者 @Isolated。此类中的测试按顺序执行,不会同时运行任何其他测试。
org.junit.jupiter.api.Assertions
是JUnit5自带的断言,该类提供了许多重载的断言方法,以及很多从JUnit4中获得的断言,还添加了一些可以使用Java8 Lambda表达式的断言方法。
import org.junit.jupiter.api.Test; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; public class AssertionsTest { @Test public void test() { //断言为真 assertTrue(true); assertTrue(() -> {return true;},"lambda"); //断言为假 assertFalse(false); assertFalse(() -> {return false;}); //断言为null assertNull(null); //断言非null assertNotNull(new Object()); //断言相等 assertEquals(1, 1); assertEquals("a", "a"); //断言数组相等 assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3}); assertArrayEquals(new String[]{"a", "b", "c"}, new String[]{"a", "b", "c"}); //断言可迭代对象相等 assertIterableEquals(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3)); //断言行匹配 assertLinesMatch(Arrays.asList("hello", "world"), Arrays.asList("hello", "world")); //断言不相等 assertNotEquals(1, 2); assertNotEquals("a", "b"); //断言相同引用 assertSame("hello", "hello"); assertSame(Integer.valueOf(1), Integer.valueOf(1)); //断言不是相同引用 assertNotSame(new Object(), new Object()); assertNotSame(Integer.valueOf(128), Integer.valueOf(128)); //断言组 assertAll(() -> { assertTrue(true); assertFalse(false); }); //断言抛出异常,必须完全一致(不可以是子类) assertThrowsExactly(NullPointerException.class, () -> { throw new NullPointerException(); }); //断言抛出异常,可以是子类 assertThrows(Exception.class, () -> { throw new NullPointerException(); }); //断言不抛出异常 assertDoesNotThrow(() -> { }); //断言超时 assertTimeout(Duration.of(10, ChronoUnit.SECONDS), () -> { Thread.sleep(9000); }); //断言类型 assertInstanceOf(Number.class, 1); } }
JUnit4自带了Hamcrest匹配器,可以使用org.junit.Assert
类的assertThat
方法。但是,JUnit5中移除了Hamcrest库的依赖,Assertions类也没有提供assertThat方法,所以需要先引入Hamcrest库的依赖。
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
如果要使用Hamcrest库,就必须使用org.hamcrest.MatcherAssert
类的assertThat
方法,重载的方法带有2个或3个参数:
要创建Matcher对象,需要使用org.hamcrest.Matchers
类提供的静态工厂方法:
anything
:绝对匹配。若要使asserti语句更具可读性,此方法非常有用nullValue,notNullValue
:测试空值或非空值closeTo、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo
:测试给定的数字是否接近、大于、大于或等于、小于、小于或等于给定的值equalTo、equalToObject
:相等匹配,其中equalToObject不要求两个对象类型相同notANumber
:当检查的Double不是数字时进行匹配comparesEqualTo
:通过Compare比较是否相等array
:用于匹配元素由指定匹配器满足的数组。仅当指定的匹配器的数量等于所检查数组的长度并且每个匹配器[i]都由数组[i]满足时,才匹配hasItemInArray
:当检查的数组至少包含一个由指定的匹配的项时,该匹配器就会匹配arrayContaining
:当检查数组中的每个项都满足指定匹配器中的对应匹配器时,该匹配器就会匹配arrayContainingInAnyOrder
:为数组创建一个与顺序无关的匹配器,当检查的数组中的每个项都满足指定匹配器中任意位置的一个匹配器时,该匹配器就会匹配arrayWithSize
:当数组的长度满足指定的匹配器时进行匹配emptyArray
:空数组anyOf
:测试是否与任一包含的匹配器相匹配(相当于引‖运算符)both
:当两个指定的匹配器都与检查的对象匹配时进行匹配allOf
:测试是否与所有包含的匹配器相匹配(相当于&运算符)either
:当指定的匹配器中的任何一个与检查对象匹配时进行匹配not
:与包含的匹配器的含义相反(如Java中的!运算符)hasProperty
:测试JavaBeans是否具有某个属性(实际匹配getter方法)hasToString
:匹配toString方法返回的内容typeCompatibleWith
:该匹配器在指定的baseType是可从检查的类赋值时匹配samePropertyValuesAs
:当被检查对象的所有JavaBean属性的值等于指定bean的相应值时进行匹配。如果任何属性被标记为已忽略,那么它们将从预期的和实际的bean中删除hasXPath
:Node的xpath匹配hasEntry、hasKey、hasValue
:测试给定映射是否具有给定条目、键或值hasltem、hasltems
:测试给定集合中是否存在一个或多个项everyItem
:每一项都匹配aMapWithSize
:当size方法返回满足指定匹配器的值时,该匹配器匹配anEmptyMap
:空maphasSize
:该匹配器在size方法返回满足指定匹配器的值时匹配empty、emptyCollectionOf、emptyIterable
:空集合contains
:为Iterable创建一个匹配器,当对检查的Iterable进行一次遍历时,该匹配器将生成一系列项,每个项在逻辑上等于指定项中的对应项。对于正匹配,检查的可迭代项的长度必须与指定项的数量相同containsInAnyOrder
:为Iterable创建一个顺序不可知的匹配器,当一次遍历所检查的Iterable产生一系列项时,该匹配器匹配,每个项在指定的匹配器中的任何位置都满足一个匹配器。对于正匹配,检查的可迭代项的长度必须与指定匹配符的数量相同containsInRelativeOrder
:为Iterable创建一个匹配器,当对检查的Iterable进行一次遍历时,该匹配器将生成一系列项,这些项在逻辑上与指定项中的对应项相等,且具有相同的相对顺序iterableWithSize
:匹配指定iterable容量in
:包含oneOf
:当检查的对象等于指定的元素之一时进行匹配any
:当检查对象是指定类型的实例时进行匹配instanceOf
:当检查对象是指定类型的实例时进行匹配samelnstance
:测试对象是否是同一实例theInstance
:该匹配器仅在检查对象与指定目标对象是同一实例时匹配equalTolgnoringCase
:测试给定字符串是否等于另一个字符串,忽略大小写equalToIgnoringWhiteSpace
:测试给定字符串是否等于另一个字符串,忽略空白containsString、endsWith、startsWith
:测试给定字符串是否包含特定字符串、以特定字符串开始或结束matchesRegex
:使用正则表达式验证字符串emptyOrNullString
:匹配null或空串emptyString
:匹配空串blankOrNullString
:匹配null或者包含零个或多个空白字符而不包含其他字符的串blankString
:匹配字符串包含零个或多个空白字符而没有其他字符的串matchesPattern
:正则匹配stringContainsInOrder
:按顺序匹配子串hasLength
:匹配指定长度的字符串is
:仅用于提高语句的可读性isA
:instanceOf(SomeClass.class)的语法糖describedAs
:包装现有的匹配器,用指定的描述覆盖其描述public class Book { String name; Double price; public Book() { } public Book(String name, Double price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } @Override public String toString() { return name + ":" + price; } }
import org.junit.jupiter.api.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.regex.Pattern; import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; public class HamcrestMatcherAssertTest { //一般匹配 @Test public void test1() { //绝对匹配 assertThat(1, anything()); //空值、非空 assertThat(null, nullValue()); assertThat(new Object(), notNullValue()); //数值比较 assertThat(1.03, is(closeTo(1.0, 0.5))); assertThat(5, greaterThan(1)); assertThat(5, lessThan(9)); assertThat(5, greaterThanOrEqualTo(5)); assertThat(5, lessThanOrEqualTo(5)); //相等 assertThat(1, equalTo(1)); assertThat(new String[] {"foo", "bar"}, equalTo(new String[] {"foo", "bar"})); assertThat(1, comparesEqualTo(1)); //不是一个数字 assertThat(Double.NaN, notANumber()); } //数组 @Test public void test2() { //完全匹配 assertThat(new Integer[]{1,2,3}, array(equalTo(1), equalTo(2), equalTo(3))); //至少包含一个 assertThat(new Integer[]{1,2,3}, hasItemInArray(1)); //每个项都匹配 assertThat(new Integer[]{1,2,3}, arrayContaining(1, 2, 3)); //无需的且每个项都匹配 assertThat(new Integer[]{1,2,3}, arrayContainingInAnyOrder(3, 2, 1)); //数组长度匹配 assertThat(new Integer[]{1,2,3}, arrayWithSize(3)); //空数组 assertThat(new Integer[]{}, emptyArray()); } //逻辑组合 @Test public void test3() { //逻辑或 assertThat(1, anyOf( equalTo(1), nullValue() )); //逻辑与 assertThat(1, allOf( equalTo(1), notNullValue(), lessThan(10) )); //逻辑非 assertThat(1, not(nullValue())); //both assertThat(1, both(equalTo(1)).and(notNullValue())); //either assertThat(1, either(equalTo(1)).or(nullValue())); } //对象相关 @Test public void test4() { Book a = new Book("XXX", 11.11); Book b = new Book("XXX", 11.11); //包含属性 assertThat(a, hasProperty("name")); assertThat(a, hasProperty("name", is("XXX"))); //匹配toString方法返回值 assertThat(a, hasToString("XXX:11.11")); //检查是否可赋值 assertThat(Number.class, typeCompatibleWith(Serializable.class)); //对象属性值相等匹配 assertThat(a, samePropertyValuesAs(b, "name", "price")); } //集合相关 @Test public void test5() { HashSet<Integer> set = new HashSet(); set.add(1); set.add(2); HashMap<String, Integer> map = new HashMap(); map.put("a", 1); map.put("b", 2); ArrayList<Integer> list = new ArrayList(); list.add(1); list.add(2); assertThat(map, hasKey("a")); assertThat(map, hasValue(1)); assertThat(map, hasEntry("a", 1)); assertThat(new HashMap<>(), anEmptyMap()); assertThat(set, hasItem(1)); assertThat(set, hasItems(1, 2)); assertThat(set, hasSize(2)); assertThat(new HashSet<>(), empty()); assertThat(new HashSet<>(), emptyIterable()); assertThat(new HashSet<Integer>(), emptyCollectionOf(Integer.class)); assertThat(set, contains(1, 2)); assertThat(set, containsInAnyOrder(2, 1)); assertThat(set, containsInRelativeOrder(1, 2)); assertThat(set, iterableWithSize(2)); assertThat(1, in(list)); assertThat(1,oneOf(1, 2, 3)); } //类型相关 @Test public void test6() { assertThat(Integer.valueOf(1), any(Integer.class)); assertThat(Integer.valueOf(1), instanceOf(Integer.class)); assertThat(Integer.valueOf(1), sameInstance(Integer.valueOf(1))); assertThat(Integer.valueOf(1), theInstance(Integer.valueOf(1))); } //字符串匹配 @Test public void test7() { //忽略大小写 assertThat("aaa", equalToIgnoringCase("Aaa")); //忽略空白符 assertThat(" my\tfoo bar ", equalToIgnoringWhiteSpace(" my foo bar")); //包含、以..开头、以...结尾 assertThat("hello world", containsString("o w")); assertThat("hello world", startsWith("he")); assertThat("hello world", endsWith("ld")); //正则匹配 assertThat("hello world", matchesRegex(Pattern.compile("^he.*"))); assertThat("hello world", matchesPattern("^he.*")); //空串 assertThat(null, emptyOrNullString()); assertThat("", emptyString()); assertThat("\t", blankString()); //按顺序匹配子串 assertThat("myfoobarbaz", stringContainsInOrder( "foo", "bar")); //长度 assertThat("hello world", hasLength(11)); } //其它 @Test public void test8() { assertThat("hello", is(equalTo("hello"))); assertThat(1, isA(Number.class)); assertThat(1, describedAs("相等", equalTo(1))); } }
Assumptions(假设)用来验证对测试所必需的先决条件的满足情况。当继续运行一个给定的测试方法没有意义时,可以使用假设。在测试报告中,这些测试被标记为中止。
JUnit5包含一组假设方法,位于org.junit.jupiter.api.Assumptions
类中,适合与Java8的Lambda表达式一起使用:
public class AssumptionsTest { @BeforeEach public void beforeEach() { //如果系统为mac os 则执行 final String osName = System.getProperty("os.name"); Assumptions.assumeTrue(() -> "Mac OS X".equals(osName)); System.out.println(osName); } @Test public void test() { //如果jdk不是1.7则执行 final String javaVersion = System.getProperty("java.vm.specification.version"); Assumptions.assumingThat(false, () -> "1.7".equals(javaVersion)); System.out.println(javaVersion); } @AfterEach public void afterEach() { //如果是x86_64架构则执行 final String osArch = System.getProperty("os.arch"); Assumptions.assumeTrue(() -> "x86_64".equals(osArch)); System.out.println(osArch); } }
Mac OS X
1.8
x86_64
在所有以前的 JUnit 版本中,测试构造函数或方法不允许有参数(至少在标准Runner实现中不允许)。作为 JUnit Jupiter 的主要变化之一,测试构造函数和方法现在都允许具有参数。这提供了更大的灵活性,并为构造函数和方法启用依赖注入。
ParameterResolver为希望在运行时动态 解析参数的测试扩展定义 API。如果测试类构造函数、测试方法或 生命周期方法接受参数,则该参数必须在运行时由注册的ParameterResolver。
目前有三个自动注册的内置解析器:
@DisplayName("TestInfo Demo") public class TestInfoDemo { public TestInfoDemo(TestInfo testInfo) { Assertions.assertEquals("TestInfo Demo", testInfo.getDisplayName()); } @BeforeEach public void init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); Assertions.assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); } @Test @DisplayName("TEST 1") @Tag("my-tag") void test1(TestInfo testInfo) { Assertions.assertEquals("TEST 1", testInfo.getDisplayName()); Assertions.assertTrue(testInfo.getTags().contains("my-tag")); } @Test void test2() { } }
public class RepetitionInfoDemo {
@RepeatedTest(10)
public void test(RepetitionInfo repetitionInfo) {
System.out.println(repetitionInfo.getCurrentRepetition() + "/" + repetitionInfo.getTotalRepetitions());
}
}
2/10
4/10
1/10
3/10
10/10
8/10
5/10
6/10
7/10
9/10
public class TestReporterDemo { @Test void reportSingleValue(TestReporter testReporter) { testReporter.publishEntry("a status message"); } @Test void reportKeyValuePair(TestReporter testReporter) { testReporter.publishEntry("a key", "a value"); } @Test void reportMultipleKeyValuePairs(TestReporter testReporter) { Map<String, String> values = new HashMap<>(); values.put("user name", "dk38"); values.put("award year", "1974"); testReporter.publishEntry(values); } }
timestamp = 2024-01-14T21:32:36.405, a key = a value
timestamp = 2024-01-14T21:32:36.405, user name = dk38, award year = 1974
timestamp = 2024-01-14T21:32:36.405, value = a status message
在org.junit.jupiter.api.extension
包下,JUnit5提供了丰富的扩展接口,通过实现这些接口,我们可以定制自己的扩展并注册到JUnit中来实现功能扩展。Extension
接口是JUnit5所有扩展接口的父接口。
Extension的子接口如下:
testDisabled
:跳过禁用的测试方法后调用testSuccessful
:测试方法成功完成后调用testAborted
:测试方法中止后调用testFailed
:测试方法失败后调用开发人员可以通过注释测试接口、测试类、测试方法或自定义组合注释@ExtendWith(…)并为要注册的扩展提供类引用,以声明方式注册一个或多个扩展。从 JUnit Jupiter 5.8 开始,@ExtendWith还可以在测试类构造函数、测试方法以及@BeforeAll、@AfterAll、 @BeforeEach和@AfterEach生命周期方法中的字段或参数上进行声明。
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RandomNumberExtension.class)
public @interface Random {
}
public class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { private final Random random = new Random(System.nanoTime()); @Override public void beforeAll(ExtensionContext context) throws Exception { final Class<?> testClass = context.getRequiredTestClass(); injectFields(testClass, null, ModifierSupport::isStatic); } @Override public void beforeEach(ExtensionContext context) throws Exception { Class<?> testClass = context.getRequiredTestClass(); Object testInstance = context.getRequiredTestInstance(); injectFields(testClass, testInstance, ModifierSupport::isNotStatic); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return parameterContext.isAnnotated(extension.Random.class) && isInteger(parameterContext.getParameter().getType()); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return this.random.nextInt(); } private void injectFields(Class<?> testClass, Object testInstance, Predicate<Field> predicate) { predicate = predicate.and(field -> isInteger(field.getType())); AnnotationSupport.findAnnotatedFields(testClass, extension.Random.class, predicate, HierarchyTraversalMode.TOP_DOWN) .forEach(field -> { try { field.setAccessible(true); field.set(testInstance, this.random.nextInt()); } catch (Exception ex) { throw new RuntimeException(ex); } }); } private static boolean isInteger(Class<?> type) { return type == Integer.class || type == int.class; } }
public class RandomNumberDemo { //在测试类的任何地方使用静态randomNumber0字段, //包括@BeforeAll或@AfterEach生命周期方法。 @Random private static Integer randomNumber0; //在测试方法和@BeforeEach //或@AfterEach生命周期方法中使用randomNumber1字段。 @Random private int randomNumber1; RandomNumberDemo(@Random int randomNumber2) { System.out.println(randomNumber2); System.out.println(randomNumber0); } @BeforeEach void beforeEach(@Random int randomNumber3) { System.out.println(randomNumber1); System.out.println(randomNumber3); } @Test void test(@Random int randomNumber4) { System.out.println(randomNumber4); } }
2018567188
1736345330
-115579494
-373582815
1409388909
这个注解也是用来注册扩展的,但是它只能被用到字段上,且相对于@ExtendWith的声明式注册方式,它是编程式的注册方式。被它标注的字段可以是static或non-static,但这个字段不能被private修饰,实际的值也不能为null:
public class ExceptionDemoTest { static class ExceptionHandler implements TestExecutionExceptionHandler { private String message; public 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())); } } @RegisterExtension static ExceptionHandler exceptionHandler = new ExceptionHandler("异常被捕获了"); @Test public void test() { throw new RuntimeException("出错啦!"); } }
如果你是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
最后,如果不想使用配置文件,你可以在VM(Run/Debug Configurations中的VM options)参数添加如下参数而达到同样的效果:-Djunit.jupiter.extensions.autodetection.enabled=true
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。