junit5 官方文档: https://junit.org/junit5/docs/current/user-guide/
https://www.ibm.com/developerworks/cn/java/j-introducing-junit5-part1-jupiter-api/index.html
What is JUnit 5?
与之前的Junit版本不同,Junit5由三部分组成:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform:
它是在JVM上启动测试框架的基础。 统一命令行、Gradle和Maven执行测试的入口。JUnit Jupiter:
包含了 JUnit 5 最新的编程模型和扩展机制。JUnit Vintage:
允许在平台上运行 JUnit 3 和 JUnit 4 的测试用例。
它的架构图如下:
JUnit 5 对 Java 运行环境的最低要求是
Java 8
。
JUnit 5 注解
Junit 4 vs Junit 5
JUnit 5 | JUnit 4 | 说明 |
---|---|---|
@Test | @Test | 被注解的方法是一个测试方法。与 JUnit 4 相同。 |
@BeforeAll | @BeforeClass | 被注解的(静态) 方法将在当前类中的所有 @Test 方法前执行一次。 |
@BeforeEach | @Before | 被注解的方法将在当前类中的每个 @Test 方法前执行。 |
@AfterEach | @After | 被注解的方法将在当前类中的每个 @Test 方法后执行。 |
@AfterAll | @AfterClass | 被注解的(静态) 方法将在当前类中的所有 @Test 方法后执行一次。 |
@Disabled | @Ignore | 被注解的方法不会执行(将被跳过),但会报告为已执行。 |
注解
JUnit Jupiter支持以下注释,用于配置测试和扩展框架。
所有核心注释都位于junit-jupiter-api
模块的org.junit.jupiter.api
包中。
Annotation | Description |
---|---|
@Test | 被注解的方法是一个测试方法。 |
@ParameterizedTest | 表示方法是参数化测试。 |
@RepeatedTest | 方法是重复测试的测试模板。 |
@TestFactory | 动态测试的测试工厂。 |
@TestTemplate | 设计为多次调用的测试用例的模板,具体取决于注册提供程序返回的调用上下文的数量。 |
@TestMethodOrder | 用于配置带注释的测试类的测试方法执行顺序;类似于JUnit 4 的@FixMethodOrder |
@TestInstance | 带注释的测试类配置测试实例生命周期。 |
@DisplayName | 类或测试方法的自定义显示名称。 |
@DisplayNameGeneratio | 声明测试类的自定义显示名称生成器。 |
@BeforeEach | 当前类中的每个@Test,@RequestTest,@ParameterizedTest或@TestFactory 方法之前 执行带注释的方法 |
@AfterEach | 同上相反 |
@BeforeAll | 被注解的(静态) 方法将在当前类中的所有 @Test 方法前`执行一次。 |
@AfterAll | 同上相反 |
@Nested | 嵌套测试类,该类类是非静态嵌套测试类。 外部类中@BeforeAll,@AfterAll 不会起作用。 |
@Tag | 在类或方法级别声明用于过滤测试的标记。 |
@Disabled | 被注解的方法不会执行(将被跳过),但会报告为已执行。 |
@Timeout | 执行超过给定持续时间时使其失败。 |
@ExtendWith | 用于以声明方式为测试类注册扩展 。 |
@RegisterExtension | 用于通过字段 以编程方式注册扩展。 这些字段是可继承的。 |
@TempDir | 用于通过生命周期方法 or 字段注入 or 参数注入 来提供临时目录; |
编写测试
maven依赖
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.1</version>
<scope>test</scope>
</dependency>
标准测试
public class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
Display Names
@DisplayName("A special test case")
public class DisplayNameDemo {
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("?")
void testWithDisplayNameContainingEmoji() {
}
}
执行结果:
Display Name Generators
自定义generator:
class IndicativeSentences extends DisplayNameGenerator.ReplaceUnderscores {
@Override
public String generateDisplayNameForClass(Class<?> testClass) {
return "顶级class:" + testClass.getSimpleName();
}
@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return "嵌套class:" + nestedClass.getSimpleName();
}
@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
String name = testClass.getSimpleName() + ' ' + testMethod.getName();
return "方法method:"+name.replace('_', ' ') + '.';
}
}
使用generator
:
@DisplayNameGeneration(IndicativeSentences.class)
public class DisplayNameGeneratorDemo {
@Test
void method0() {
}
@Nested
@DisplayNameGeneration(IndicativeSentences.class)
class ChildTestCase {
@Test
void method1() {
}
@Test
void method2() {
}
}
}
运行结果:
设置默认的Display Name Generator
在src/test/resources/junit-platform.properties
下添加如下配置:
junit.jupiter.displayname.generator.default = IndicativeSentences
断言(Assertions)
如果断言失败,测试会在断言所在的代码行上停止,并生成断言失败报告。如果断言成功,测试会继续执行下一行代码。
工具类
class Calculator {
int add(int a, int b){
return a + b;
}
int multiply(int a, int b){
return a * b;
}
int divide(int a, int b){
return a / b;
}
}
@Data
@AllArgsConstructor
class Person {
private String firstName;
private String lastName;
}
标准断言
@Test
public void standardAssertions() {
assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2),"不相等");
//避免不必要地构建复杂的消息, 只有才失败时,才构建语句
assertTrue('a' < 'b', () -> "不相等2");
}
分组
在分组断言中,所有断言都被执行,所有断言都被执行
,将一起报告失败。
@Test
void groupedAssertions() {
assertAll("person",
() -> assertEquals("Jane", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
}
分组嵌套
在代码块中,如果断言失败了,将跳过同一块中的后续代码。
@Test
void dependentAssertions() {
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName);
//只有在上面的assertNotNull() 执行通过后方可执行
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("e"))
);
},
() -> {
// 分组断言,独立处理
// 即使上面的断言失败,此断言仍然执行
String lastName = person.getLastName();
assertNotNull(lastName);
//只有在上面的assertNotNull() 执行通过后方可执行
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
}
其他APIs
/**
* 如果异常类型不匹配,则抛出异常
* 如果异常类型匹配,则获取返回值,继续执行
* @param expectedType 期待的异常类型
* @param executable 执行方法
*/
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable,
Supplier<String> messageSupplier);
/**
* 执行超时抛出异常
* @param timeout 超时时间
* @param executable 无返回值的方法
*/
public static void assertTimeout(Duration timeout, Executable executable);
/**
* 执行成功,返回返回值; 超时则抛出异常;
* @param timeout 超时时间
* @param supplier 带返回值的方法
* @return [description]
*/
public static <T> T assertTimeout(Duration timeout, ThrowingSupplier<T> supplier);
// executable 使用另外的线程执行
public static void assertTimeoutPreemptively(Duration timeout, Executable executable) ;
前置条件 (Assumption)
前置条件 (Assumption) 与断言类似,但前置条件必须为 true,否则测试将中止。
assumeTrue
@Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV")));
// 只有上面的假设成立,方可执行后续的代码
}
@Test
void testOnlyOnDeveloperWorkstation() {
//假设不成立,抛出异常,并打印后续messageSupplier的异常提示信息
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "不是DEV环境");
}
assumingThat
当条件满足时执行后续executable,不满足直接跳过(不抛出异常)
@Test
void testInAllEnvironments() {
//assumingThat: 当条件满足时执行后续executable,不满足直接跳过(不抛出异常)
assumingThat("CI".equals(System.getenv("ENV")),
() -> {
//仅在 CI 环境运行
assertEquals(2, calculator.divide(4, 2));
});
}
Disabled
@Disabled
可以使用在类上,也可以在方法上使用,强烈建议在使用@Disable时,增加一段说明文字
。
@Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo {
@Test
void testWillBeSkipped() {
}
}
class DisabledTestsDemo {
@Disabled("Disabled until bug #42 has been resolved")
@Test
void testWillBeSkipped() {
}
@Test
void testWillBeExecuted() {
}
}
Conditional
System Conditions
@EnabledOnOs
和@EnabledOnOs
,
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
}
//enable 多个参数
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
}
//disable
@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
}
自定义注解:
//自定义注解类型
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
public @interface TestOnMac {
}
//使用自定义注解类型
@TestOnMac
void testOnMac() {
}
Jre Conditions
@Test
@EnabledOnJre(JAVA_8)
void onlyOnJava8() {
}
@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
}
@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
}
SystemProperty Conditions
虚拟机的变量形,如: -Djavaxxxx。
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
}
@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
}
Environment conditions
操作系统的环境变量。
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
}
@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
}
script conditions(实验阶段 ,不建议使用)
@Test // Static JavaScript expression.
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
}
@Test
@EnabledIf("Math.random() < 0.314159")
void mightNotBeExecuted() {
}
@Test // Regular expression testing bound system property.
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
public void disabledOn32BitArchitectures() {
assertFalse(System.getProperty("os.arch").contains("32"));
}
@Test
@EnabledIf("'CI' == systemEnvironment.get('ENV')")
void onlyOnCiServer() {
assertTrue("CI".equals(System.getenv("ENV")));
}
Tagging and Filtering
@Tag("fast")
@Tag("model")
public class TaggingDemo {
@Test
@Tag("taxes")
void testingTaxCalculation() {
}
//自定义注解
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
@Tag("fast")
static @interface Fast{
}
//使用自定义注解
@Test
@Fast
void testingFast() {
}
}
过滤tag — maven配置
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<!-- groups: 表示需要包含的tag,或表达式
excludedGroups: 表示需要排除的tag,或表达式
-->
<!--
<configuration>
<groups>acceptance | !feature-a</groups>
<excludedGroups>integration, regression</excludedGroups>
</configuration>
-->
</plugin>
</plugins>
</build>
经过测试,未起作用, 待后续完善。
Test Order
排序支持三种方式:
OrderAnnotation:
- 按照注解顺序排序Alphanumeric:
按照方法名称 和 参数个数排序Random:
随机排序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderedTestsDemo {
@Test
@Order(1)
void a() {
}
@Test
@Order(2)
void c() { }
@Test
@Order(3)
void b() { }
}
Nested Tests
package junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.EmptyStackException;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Trump
* @version 1.0.0
* @Description
* @createTime 2019-09-04 14:31
*/
@DisplayName("class_1")
public class TestingAStackDemo {
@BeforeEach
@DisplayName("method_1")
void method_1() {
System.out.println("method_1()");
}
@Nested
@DisplayName("class_1_1")
class WhenNew {
@BeforeEach
@DisplayName("method_1_1")
void method_1_1() {
System.out.println("method_1_1()");
}
@Test
@DisplayName("method_1_2")
void method_1_2() {
/**
* 执行结果:
* method_1()
* method_1_1()
*/
}
@Nested
@DisplayName("class_1_1_1")
class AfterPushing {
@BeforeEach
@DisplayName("method_1_1_1")
void method_1_1_1() {
System.out.println("method_1_1_1()");
}
@Test
@DisplayName("method_1_1_2")
void method_1_1_2() {
/**
* 执行结果:
* method_1()
* method_1_1()
* method_1_1_1()
*/
}
}
}
}
@Nested
允许多层嵌套(non-static类
)。 内部类测试方法执行时,会执行所有外部类
({outer,outer.outer....}
)的@BeforeEach,@AfterEach
方法。
嵌套类无法定义自身的@BeforeAll
和@AfterAll
方法,是因为内部类是不允许定义静态方法的。