单元测试之JUnit5使用
概述
我们常说的JUnit5包含三部分:
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: 是JVM上启动测试框架的基础。提各种平台或者IDE提醒接入测试框架的能力,日常编写测试代码时不用太关心此模块
- JUnit Vintage: 一个兼容Junit3/4的测试引擎,如果不混用Junit5和低版本,不用引入此模块
- Junit Jupiter: Junit5的核心依赖模块,是编程模型(提供我们编写测试代码时各种依赖)和扩展模型(提供使用或编写各种扩展的依赖)的组合。
依赖
Junit5需要运行在Java8或更高的版本
编写测试准备
以Maven项目为例
添加Junit5依赖
<dependencies> <!-- JUnit 5 核心依赖 除了参数化测试外的 编程模型和扩展模型的核心包--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <!-- JUnit 5 参数化测试的依赖,不使用@ParameterizedTest的话可以不引入,但是怎么可能不使用呢?--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> <!-- JUnit 5 测试引擎--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit-jupiter.version}</version> <scope>test</scope> </dependency> </dependencies>
配置Maven Surefire 插件
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <includes> <include>**/*Test.java</include> </includes> </configuration> </plugin> </plugins> </build>
编写测试类
按照Maven惯例在 src/test/java
中创建测试类:
两个小概念
- 测试方法:任何直接注解或元注解了 @Test、@RepeatedTest、@ParameterizedTest、@TestFactory 或 @TestTemplate 的实例方法。
- 测试类:任何顶层类、static 成员类或@Nested 类,其中包含至少一个测试方法,即容器。测试类不能是 abstract 的,并且必须具有单个构造函数。
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { @Test void testAdd() { Calculator calculator = new Calculator(); assertEquals(5, calculator.add(2, 3)); } @Test void testDivideByZero() { Calculator calculator = new Calculator(); assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0)); } }
参数化测试
前天必须引入junit-jupiter-params依赖
我常用的时@ValueSource和@MethodSource
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; public class CalculatorParameterizedTest { // 被测试的类 private final Calculator calculator = new Calculator(); // 1. 基础参数化测试:单参数输入(测试平方) @ParameterizedTest @ValueSource(ints = {2, -3, 0}) // 提供三个测试输入 void testSquare(int number) { int result = calculator.square(number); assertEquals(number * number, result); } // 2. 多参数输入测试(测试加法) @ParameterizedTest @CsvSource({ "5, 3, 8", // 5+3=8 "0, 0, 0", // 0+0=0 "-2, 5, 3" // -2+5=3 }) void testAdd(int a, int b, int expected) { int result = calculator.add(a, b); assertEquals(expected, result); } // 3. 参数来源为方法(测试除法) @ParameterizedTest @MethodSource("provideDivisionTestData") // 从方法获取数据 void testDivide(int dividend, int divisor, int expected) { int result = calculator.divide(dividend, divisor); assertEquals(expected, result); } // 提供测试数据的方法(必须是静态的) private static Stream<Arguments> provideDivisionTestData() { return Stream.of( Arguments.of(10, 2, 5), Arguments.of(0, 5, 0), Arguments.of(-9, 3, -3) ); } // 4. 参数化异常测试(除零异常) @ParameterizedTest @CsvSource({"10, 0", "-5, 0"}) void testDivideByZero(int dividend, int divisor) { assertThrows(ArithmeticException.class, () -> calculator.divide(dividend, divisor)); } } // 计算器类(新增平方方法) class Calculator { int add(int a, int b) { return a + b; } int square(int n) { return n * n; } int divide(int a, int b) { return a / b; } }
生命周期
什么是生命周期方法?
任何直接注解或元注解了 @BeforeAll、@AfterAll、@BeforeEach 或 @AfterEach 的方法。
注释解释
注解 | 作用 | 执行顺序 |
---|---|---|
@BeforeAll |
整个测试类执行前运行一次(常用于初始化全局资源,如数据库连接) | 1 |
@BeforeEach |
每个测试方法执行前运行(常用于重置测试数据或准备环境) | 2 |
@Test |
标记一个测试方法 | 3 |
@AfterEach |
每个测试方法执行后运行(常用于清理资源,如关闭文件) | 4 |
@AfterAll |
整个测试类执行后运行一次(常用于释放全局资源,如关闭数据库连接) | 5 |
例子
import org.junit.jupiter.api.*; public class CalculatorLifecycleTest { // 1. 整个测试类初始化(只执行一次) @BeforeAll static void initAll() { System.out.println("==== 测试类开始 ===="); } // 2. 每个测试方法前的初始化(每个测试方法执行前都会调用) @BeforeEach void initEach() { System.out.println("\n--- 准备执行新测试 ---"); } // 测试方法 1:加法测试 @Test void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(2, 3); Assertions.assertEquals(5, result); System.out.println("加法测试通过 ✅"); } // 测试方法 2:除法测试 @Test void testDivide() { Calculator calculator = new Calculator(); Assertions.assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0)); System.out.println("除法异常测试通过 ✅"); } // 3. 每个测试方法后的清理(每个测试方法执行后都会调用) @AfterEach void cleanUpEach() { System.out.println("--- 当前测试完成 ---"); } // 4. 整个测试类结束后的清理(只执行一次) @AfterAll static void cleanUpAll() { System.out.println("\n==== 测试类结束 ===="); } } // 简单的计算器类 class Calculator { int add(int a, int b) { return a + b; } int divide(int a, int b) { return a / b; } }
执行结果
==== 测试类开始 ==== --- 准备执行新测试 --- 加法测试通过 ✅ --- 当前测试完成 --- --- 准备执行新测试 --- 除法异常测试通过 ✅ --- 当前测试完成 --- ==== 测试类结束 ====
定义及使用扩展
参考
官网:https://junit.cn/junit5/docs/current/user-guide/#overview
以上例子都是DeepSeek写的