Java JUnit5 使用小结
在我们的日常开发中,代码一边编码一边自测是常有的事,做好单元测试也是一名开发应该掌握的技能,不说测试搞得多么强,至少会基本的,会功能测试,会性能测试。今天来学习下 单元测试。
1.JUnit5介绍
现在主要版本是 JUnit5,所以后面的内容也都是基于 JUnit5 做相关的介绍。JUnit5 是 JUnit 单元测试框架的重大升级,需要运行在 Java8 以上的环境。
JUnit5可以理解为是由三个不同而子项目构成:
- 1.JUnit Platform,用于JVM上启动测试框架的基础服务,提供命令行,IDE和构建工具等方式执行测试的支持。
- 2.JUnit Jupiter,包含 JUnit 5 新的编程模型和扩展模型,主要就是用于编写测试代码和扩展代码。
- 3.JUnit Vintage,用于在JUnit 5 中兼容运行 JUnit3.x 和 JUnit4.x 的测试用例。
JUnit5目前的主要特性:
- 提供全新的断言和测试注解,支持测试类内嵌
- 更丰富的测试方式:支持动态测试,重复测试,参数化测试等
- 实现了模块化,让测试执行和测试发现等不同模块解耦,减少依赖
- 提供对 Java 8 的支持,如 Lambda 表达式,Sream API等。
2.测试环境
- Java 21
- Junit 5.10.0
3.Maven依赖
本篇学习内容涉及的依赖如下:
<!-- 基础测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.10.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.10.0</version> </dependency> <!-- 带参测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.10.0</version> </dependency>
4.典型注解
@BeforeAll
修饰 static 方法,定义整个测试类在开始前的操作,比如一些初始化的操作。
@AfterAll
修饰 static 方法,定义整个测试类在结束时的操作,比如一些清理工作。
@BeforeEach && @AfterEach
标注在每个测试用例方法,表示方法开始前或结束时的执行,负责该测试用例所需要的的运行环境的准备和销毁。
@Test
表示这个测试方法是一个测试用例。
由上面的这些注解,我们可以做出如下流程来表示单元测试的完整过程:
@DisplayName
可以加在类上,也可以加在测试方法上,相当于一个显示名。
@Disable
相当于禁用当前的测试方法,测试时就会忽略该方法。
@RepeatedTest
表示该方法需要重复运行,具体几次,可以通过参数传入。
基于上面的介绍,我们写一个简单的类测试:
package org.example; import org.junit.jupiter.api.*; import java.time.Duration; @DisplayName("我的第一个测试用例") public class MyFirstTestCaseTest { @BeforeAll public static void init() { System.out.println("初始化数据"); } @AfterAll public static void cleanup() { System.out.println("清理数据"); } @BeforeEach public void tearUp() { System.out.println("当前测试方法开始"); } @AfterEach public void tearDown() { System.out.println("当前测试方法结束"); } @DisplayName("我的第一个测试") @Test public void testFirstTest() { System.out.println("我的第一个测试开始"); } @DisplayName("我的第二个测试") @Test public void testSecondTest() { System.out.println("我的第二个测试开始"); } @DisplayName("我的第三个测试") @Disabled @Test public void testThirdTest() { System.out.println("我的第三个测试开始测试"); } @DisplayName("我的第四个测试-重复测试") @RepeatedTest(value = 3, name = "{displayName} 第 {currentRepetition} 次") public void repeatedTest() { System.out.println("正在执行重复测试"); } }
5.断言
在测试方法中,我们常常是给定一个预期的值和测试的结果值作比较,看对应的结果怎样,由此就由相关断言:
- 断言相等,assertEqual
- 断言不等,assertNotEqual
- 多个断言,assertAll
- 断言空或非空,assertNull/assertNotNull
- 断言 true或false,assertTrue/assertFalse
- 超时断言,assertTimeout/assertTimeoutPreemptively
- 断言实例,assertInstanceOf
- 断言异常,assertThrows
由于断言类包含内容很多,每个方法实际很多重载,这里仅挑选几个重点说说,其他都是类似的。
demo
@DisplayName("我的第五个测试-单个断言") @Test public void testSingleAssertion() { Integer num = 1; Assertions.assertEquals(num, 1); } @DisplayName("我的第六个测试-多断言") @Test public void testGroupAssertions() { int[] nums = {0, 1, 2, 3, 4}; Assertions.assertAll("nums", () -> Assertions.assertEquals(nums[0], 0), () -> Assertions.assertEquals(nums[1], 1), () -> Assertions.assertEquals(nums[2], 2), () -> Assertions.assertEquals(nums[3], 3), () -> Assertions.assertEquals(nums[4], 4) ); } @DisplayName("我的第七个测试-超时操作") @Test public void testShouldCompleteInOneSecond() { // 无法做到时间的精确匹配 Assertions.assertTimeoutPreemptively(Duration.ofSeconds(1), () -> Thread.sleep(999)); } @DisplayName("我的第八个测试-异常测试") @Test public void testAssertThrowsException() { String str = null; // str 作为传入参数,会报非法传参异常,所以可以正常断言到 Assertions.assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }
6.带参数的测试方法
有的时候我们需要一些带参数的测试方法,比如一次测试一个参数不够,那就来一组参数,或者有的传参需要多个参数怎么办。
这里我们用到了 Junit5的 params 包提供功能,这也是上面我们在依赖中添加的依赖项。
在有带参数的单元测试中主要介绍几个常用的,其他感兴趣可以看看源码。
@ParameterizedTest
此处可以用来代替 @Test 注解,一样的功效。
@ValueSource
指定我们的传入的这一组参数,可以是:
- ints
- strings
- classes等
包含类型覆盖基本类型。
@CsvSource
规定了传入的多个入参的组合形式,默认用“,”分隔。
下面是个简单的 demo:
package org.example; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; public class ParameterUnitTest { @ParameterizedTest @ValueSource(ints = {2, 4, 8, 10}) public void testIsEvenNumber(int num) { Assertions.assertEquals(0, num % 2); } @ParameterizedTest @ValueSource(strings = {"Effective Java", "C Plus Plus"}) public void testPrintTitle(String title) { System.out.println(title); } // 多参数 @ParameterizedTest @CsvSource({"1,One", "2,Two"}) public void testDataFromCSV(long id, String name) { System.out.printf("id: %d, name: %s\n", id, name); } }
以上这些都是一些基本用法,有的时候我们需要依赖其他类,所以就会有 mock打桩 的需求,这是后话了,掌握这些基本用法能覆盖相当的开发自测了。
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-11-21 0090-子集II
2021-11-21 0078-子集
2021-11-21 0093-复原IP地址
2021-11-21 0131-分割回文串
2021-11-21 0216.-组合总和 III
2021-11-21 0040-组合总和II
2021-11-21 0039-组合总和