JUnit 5 教程:从入门到精通,掌握现代Java单元测试

JUnit 5 教程

在本 JUnit 教程中,我们将通过示例介绍 JUnit 5 的基础知识和新功能。在 Java 世界中,JUnit 是用于对 Java 代码进行单元测试的流行框架之一。JUnit 主要帮助开发人员在 JVM 上测试自己的代码。

JUnit 5 架构

JUnit 5 架构

JUnit 平台

  • 在 JVM 上启动测试框架
  • 拥有 TestEngine API,用于构建可在 JUnit 平台上运行的测试框架

JUnit Jupiter(木星)

  • 融合了用于编写测试的新编程模型和用于扩展的扩展模型
  • 新增了诸如 @BeforeEach、@AfterEach、@AfterAll、@BeforeAll 等注解

JUnit Vintage(传统版)

  • 支持在新平台上执行以前的 JUnit 3 和 4 版本测试

JUnit 的 Maven 依赖

为了在项目中实现基于 JUnit 5 的测试用例,需要将以下依赖项添加到项目的 pom.xml 文件中。

  • JUnit 5 库
<dependency>
     <groupId>org.junit.jupiter</groupId>
     <artifactId>junit-jupiter-engine</artifactId>
     <version>5.1.1</version>
     <scope>test</scope>
</dependency>
<dependency>
     <groupId>org.junit.platform</groupId>
     <artifactId>junit-platform-runner</artifactId>
     <version>1.1.1</version>
     <scope>test</scope>
</dependency>
  • JUnit 5 Maven Surefire 插件(用于在 IDE 不支持 JUnit 5 的情况下执行单元测试,如果 IDE 支持则无需此项)
<plugin>
     <artifactId>maven-surefire-plugin</artifactId>
     <version>2.19.1</version>
     <dependencies>
          <dependency>
               <groupId>org.junit.platform</groupId>
               <artifactId>junit-platform-surefire-provider</artifactId>
               <version>1.0.2</version>
          </dependency>
     </dependencies>
</plugin>

JUnit 5 新功能

运行时需要 Java 8 或更高版本。但是可以使用之前 Java 版本编译的代码进行测试。其中引入了各种新功能。

JUnit 注解

以下是一些常用的注解:

注解 描述
@Test 表示一个测试方法
@DisplayName 为测试类或测试方法声明自定义显示名称
@BeforeEach 表示带注解的方法应在每个测试方法之前执行
@AfterEach 表示带注解的方法应在每个测试方法之后执行
@BeforeAll 表示带注解的方法应在所有测试方法之前执行
@AfterAll 表示带注解的方法应在所有测试方法之后执行
@Disable 用于禁用测试类或测试方法
@Nested 表示带注解的类是一个嵌套的、非静态的测试类
@Tag 声明用于过滤测试的标签
@ExtendWith 注册自定义扩展
package com.Olivia;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JUnit5Sample1Test {

  @BeforeAll
  static void beforeAll() {
    System.out.println("**--- 在此类的所有测试方法执行前,仅执行一次 ---**");
  }

  @BeforeEach
  void beforeEach() {
    System.out.println("**--- 在此类的每个测试方法执行前执行 ---**");
  }

  @Test
  void testMethod1() {
    System.out.println("**--- 测试方法1 执行 ---**");
  }

  @DisplayName("带条件的测试方法2")
  @Test
  void testMethod2() {
    System.out.println("**--- 测试方法2 执行 ---**");
  }

  @Test
  @Disabled("实现待定")
  void testMethod3() {
	  System.out.println("**--- 测试方法3 执行 ---**");
  }

  @AfterEach
  void afterEach() {
    System.out.println("**--- 在此类的每个测试方法执行后执行 ---**");
  }

  @AfterAll
  static void afterAll() {
    System.out.println("**--- 在此类的所有测试方法执行后,仅执行一次 ---**");
  }


}
Junit 5 tutorial, eclipse run junit test

JUnit 断言

每个测试方法必须使用断言来对条件进行评估,以使测试能够继续执行。JUnit Jupiter 的断言存储在 org.junit.jupiter.api.Assertions 类中,所有方法都是静态的。

断言 描述
assertEquals(expected, actual) 当预期值不等于实际值时失败
assertFalse(expression) 当表达式不为假时失败
assertNull(actual) 当实际值不为 null 时失败
assertNotNull(actual) 当实际值为 null 时失败
assertAll() 将多个断言分组,即使其中一个或多个断言失败,所有断言也会被执行
assertTrue(expression) 当表达式不为真时失败
assertThrows() 预期被测试的类会抛出异常
@Test
void testAssertEqual() {
	 assertEquals("ABC", "ABC");
	 assertEquals(20, 20, "可选断言消息");
	 assertEquals(2 + 2, 4);
}

@Test
void testAssertFalse() {
	 assertFalse("FirstName".length() == 10);
	 assertFalse(10 > 20, "断言消息");
}

@Test
void testAssertNull() {
     String str1 = null;
	 String str2 = "abc";
	 assertNull(str1);
	 assertNotNull(str2);	
}

@Test
void testAssertAll() {
	 String str1 = "abc";
	 String str2 = "pqr";
	 String str3 = "xyz";
	 assertAll("数字",
	      () -> assertEquals(str1,"abc"),
		  () -> assertEquals(str2,"pqr"),
		  () -> assertEquals(str3,"xyz")
	 );
	 //取消注释以下代码并理解每个断言的执行
     /*assertAll("数字",
		  () -> assertEquals(str1,"abc"),
		  () -> assertEquals(str2,"pqr1"),
		  () -> assertEquals(str3,"xyz1")
	 );*/
}

@Test
void testAssertTrue() {
	 assertTrue("FirstName".startsWith("F"));
	 assertTrue(10 < 20, "断言消息");
}

@Test
void testAssertThrows() {
	 Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
	      throw new IllegalArgumentException("发生非法参数异常");
	 });
	 assertEquals("发生非法参数异常", exception.getMessage());
}

JUnit5 的导入

它的测试类需要导入 org.junit.jupiter.api.Test 语句而不是 org.junit.Test。此外,测试方法不需要公共修饰符和本地包。

import org.junit.jupiter.api.Test;

JUnit5 的假设

假设是 org.junit.jupiter.api.Assumptions 类中的静态方法。只有满足指定条件时,它们才会执行测试,否则测试将被中止。中止的测试不会导致构建失败。当假设失败时,会抛出 org.opentest4j.TestAbortedException 并跳过该测试。

假设 描述
assumeTrue 当肯定条件成立时执行 lambda 主体,否则测试将被跳过
assumeFalse 当否定条件成立时执行 lambda 主体,否则测试将被跳过
assumingThat 如果假设成立,测试方法的一部分将执行;assumingThat() 中 lambda 之后的所有内容都将执行,无论假设是否成立

@Test
void testAssumeTrue() {
     boolean b = 'A' == 'A';
     assumeTrue(b);
     assertEquals("Hello", "Hello");
}

@Test
@DisplayName("test executes only on Saturday")
public void testAssumeTrueSaturday() {
     LocalDateTime dt = LocalDateTime.now();
     assumeTrue(dt.getDayOfWeek().getValue() == 6);
     System.out.println("further code will execute only if above assumption holds true");
}

@Test
void testAssumeFalse() {
     boolean b = 'A' != 'A';
     assumeFalse(b);
     assertEquals("Hello", "Hello");
}

@Test
void testAssumeFalseEnvProp() {
     System.setProperty("env", "prod");
     assumeFalse("dev".equals(System.getProperty("env")));
     System.out.println("further code will execute only if above assumption hold");
}

@Test
void testAssumingThat() {
     System.setProperty("env", "test");
     assumingThat("test".equals(System.getProperty("env")),
          () -> {
               assertEquals(10, 10);
               System.out.println("perform below assertions only on the test env");
               });

     assertEquals(20, 20);
     System.out.println("perform below assertions on all env");
}

JUnit 嵌套测试类

嵌套测试允许创建嵌套类并执行其中的所有测试方法。内部类必须是非静态的。只需使用 @Nested 注解内部类,内部类中的所有测试方法都将被执行。

@BeforeAll
static void beforeAll() {
     System.out.println("**--- JUnit5Sample4Test :: beforeAll :: 在所有测试方法执行前执行一次 ---**");
}
 
@BeforeEach
void beforeEach() {
	 System.out.println("**--- JUnit5Sample4Test :: beforeEach :: 在每个测试方法执行前执行 ---**");
}

@AfterEach
void afterEach() {
	 System.out.println("**--- JUnit5Sample4Test :: afterEach :: 在每个测试方法执行后执行 ---**");
}

@AfterAll
static void afterAll() {
	 System.out.println("**--- JUnit5Sample4Test :: afterAll :: 在所有测试方法执行后执行一次 ---**");
}
 
     @Nested
     class InnerClass {
 
          @BeforeEach
          void beforeEach() {
               System.out.println("**--- InnerClass :: beforeEach :: 在每个测试方法执行前执行 ---**");
          }
 
          @AfterEach
          void afterEach() {
        	   System.out.println("**--- InnerClass :: afterEach :: 在每个测试方法执行后执行 ---**");
          }
 
          @Test
          void testMethod1() {
        	   System.out.println("**--- InnerClass :: testMethod1 :: 执行测试方法1 ---**");
          }
 
          @Nested
          class InnerMostClass {
 
               @BeforeEach
               void beforeEach() {
                    System.out.println("**--- InnerMostClass :: beforeEach :: 在每个测试方法执行前执行 ---**");
               }
 
               @AfterEach
               void afterEach() {
            	    System.out.println("**--- InnerMostClass :: afterEach :: 在每个测试方法执行后执行 ---**");
               }
 
               @Test
               void testMethod2() {
            	    System.out.println("**--- InnerMostClass :: testMethod2 :: 执行测试方法2 ---**");
               }
        }
    }

JUnit 测试异常

在某些情况下,方法有望根据特定条件抛出异常。如果给定的方法未抛出指定的异常,assertThrows 将导致测试失败。

Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
     throw new IllegalArgumentException("非法参数异常发生");
});
assertEquals("非法参数异常发生", exception.getMessage());

JUnit 测试执行

单元测试可以通过多种方式来执行,其中两种方式如下:

  • 使用 Eclipse IDE Oxygen.3a (4.7.3a) 版本,打开要执行的测试文件。右键单击文件并选择“运行方式 (Runs As)”,然后选择“JUnit 测试 (JUnit Test)”。
  • 在 Windows 命令行中使用 mvn test 命令。

总结

我们已经通过一些例子探索了 JUnit 5 及其新特性。我们还了解了如何使用 JUnit 注解、断言、假设、异常以及编写嵌套测试类。

您可以从我们的 GitHub 仓库中下载完整的示例项目。

bannerAds