JUnit5 单元测试详解

server/2025/2/12 5:44:12/

目录

一、什么是单元测试,为什么要进行单元测试

二、JUnit 框架介绍

1.如何引进这些Jar包?

2.如何查看是否引进?

⑴. Project(项目)

⑵. Modules(模块)

⑶. Libraries(库)

⑷. Facets(方面)

⑸. Artifacts(工件)

⑹. SDKs

三、如何编写单元测试

3.1 单元测试类的命名

3.2 编写单元测试方法

示例

四、什么是期望值和实际值?

1.常见的断言方法

2.示例代码

3.自定义注解 Test

1. @interface 关键字

2. public 访问修饰符

3. @Testable 元注解

五、 JUnit 5 常用注解

5.1 运行前后的注解

示例

1.@BeforeAll  和@AfterAll 

2.@BeforeEach和@AfterEach 

六、 解决单元测试中 Scanner 失效问题

解决方法步骤

七、其他高级功能

7.1 忽略测试

7.2 超时测试


一、什么是单元测试,为什么要进行单元测试


单元测试(Unit Test) 是对软件中的最小可测试单元(如一个方法或一个类)进行独立测试,确保其行为符合预期。
在大型项目中,确保每个单元都是正确的,有助于降低错误传播的风险,提高代码质量,增强可维护性。

单元测试的优势


①及早发现错误:在开发阶段就发现并修复 Bug,降低修复成本。
②提高代码质量:促进代码解耦,使代码更加模块化、可复用。
③增强代码可维护性:修改代码时,能够快速验证新代码是否影响原有功能。
④方便回归测试:自动化测试能减少手动测试的工作量,提高效率。

二、JUnit 框架介绍

JUnit 是 Java 语言中最流行的单元测试框架之一。JDK 不自带 JUnit,需要手动引入。
最新的 JUnit 版本是 JUnit 5,由 JUnit Platform、JUnit Jupiter 和 JUnit Vintage 组成。

如何引入 JUnit 5
如果使用 Maven,在 pom.xml 文件中添加:

<dependencies><!-- JUnit 5 依赖 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.9.2</version></dependency>
</dependencies>

如果使用 Gradle:

dependencies {testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
}

单元测试需要引入JUnit框架,JUnit框架在JDK中没有,需要额外引入,也就是引入JUnit框架的class文件(jar包)

1.如何引进这些Jar包?

1.在模块下新建目录,用于存放jar包

下载 JUnit Jar 包

  • 访问 JUnit 的官方 Maven 仓库(https://mvnrepository.com/ ),搜索 JUnit,选择合适的版本下载对应的 jar 包。例如,JUnit 5 的核心库junit-jupiter-apijunit-jupiter-engine,以及 JUnit Vintage(用于支持旧的 JUnit 3 和 JUnit 4 测试)的junit-vintage-engine
  • 一般 Java 项目的目录结构为src存放源代码,lib存放外部库文件。在项目根目录下创建lib文件夹,并将下载好的 JUnit Jar 包复制到lib文件夹中。

2.把jar包下载好放进去

3.配置类路径(Classpath)



点ok即可


2.如何查看是否引进?

1.点Project Structure(项目结构)

⑴. Project(项目)

  • 作用:定义整个项目的基本设置,这些设置会影响项目中的所有模块。
  • 主要设置项
    • Project name:项目的名称,在文件系统和 IDE 中标识该项目。
    • Project SDK:选择项目要使用的软件开发工具包(SDK),比如 Java SDK。如果没有合适的 SDK,可以点击 “New” 来添加新的 SDK。
    • Project language level:指定项目使用的 Java 语言级别,不同的语言级别支持不同的 Java 特性。
    • Project compiler output:设置项目编译输出的目录,编译生成的 .class 文件会存放在这里。

⑵. Modules(模块)

  • 作用:模块是项目的组成部分,每个模块可以有自己独立的设置和依赖。
  • 主要设置页签
    • Sources
      • 定义源代码、测试代码、资源文件和测试资源文件的目录。通常,源代码目录显示为蓝色,测试代码目录显示为绿色,资源目录显示为黄色。
      • 可以设置排除目录,这些目录不会被编译或参与项目构建。
    • Paths
      • 配置模块的输出路径,包括编译输出目录和测试编译输出目录。
    • Dependencies
      • 添加模块所需的依赖项,如 JAR 文件、库、其他模块等。可以通过不同的作用域(如 Compile、Test、Runtime 等)来指定依赖项的使用范围。

所以,这样可以看到已经引进了junit单元测试所需jar包

⑶. Libraries(库)

  • 作用:管理项目中使用的外部库。
  • 设置说明
    • 可以添加本地的 JAR 文件、文件夹或从 Maven、Gradle 等仓库中下载的依赖库。
    • 这些库会被项目中的模块引用,为项目提供额外的功能。

⑷. Facets(方面)

  • 作用:用于为模块添加特定的功能或框架支持。
  • 常见示例
    • 例如,添加 Web 方面可以使模块支持 Web 开发,包括配置 Web 资源目录、部署描述符等。

⑸. Artifacts(工件)

  • 作用:定义项目的打包方式和输出结果。
  • 常见类型及设置
    • 常见类型:如 JAR、WAR、EAR 等。
    • 设置内容:可以指定哪些模块、资源和依赖项会被包含在打包结果中,以及打包的输出路径。

⑹. SDKs

  • 作用:管理项目使用的 SDK,包括 Java SDK、Android SDK 等。
  • 操作内容
    • 可以添加、删除和配置不同版本的 SDK,还可以设置 SDK 的路径和相关参数。

三、如何编写单元测试

3.1 单元测试类的命名

单元测试类的命名通常遵循:

  • 被测试类名 + Test
  • 例如,测试 Calculator 类时,单元测试类命名为 CalculatorTest

3.2 编写单元测试方法

  • 方法需要使用 @Test 注解
  • 返回值类型必须是 void
  • 方法不能有参数
  • 方法名建议使用 testXxx

示例

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;class CalculatorTest {// 被测试方法public int add(int a, int b) {return a + b;}// 单元测试方法@Testvoid testAdd() {CalculatorTest calculator = new CalculatorTest();int actualValue = calculator.add(2, 3);  // 实际值int expectedValue = 5;  // 期望值assertEquals(expectedValue, actualValue, "加法计算错误");}
}

四、什么是期望值和实际值?

 ● 期望值(Expected Value):你认为代码正确运行后应该返回的值
 ● 实际值(Actual Value):代码实际运行后的返回值
JUnit 提供了 Assertions 断言方法 来检查期望值和实际值是否相等,例如:

在 Java 的 JUnit 测试框架中,assertEquals 是一个非常重要的断言方法,用于验证预期值和实际值是否相等。

assertEquals(期望值, 实际值, "错误信息");

1.常见的断言方法

断言方法作用
assertEquals(expected, actual) 断言两个值是否相等
assertNotEquals(expected, actual) 断言两个值不相等
assertTrue(condition) 断言条件为 true
assertFalse(condition) 断言条件为 false
assertNull(object)断言对象为 null
assertNotNull(object)  断言对象不为 null
assertThrows(Exception.class, () -> 方法调用) 断言是否抛出指定异常

2.示例代码

// 自定义的 Math 类
class MyMath {public static int sum(int a, int b) {return a + b;}public static int sub(int a, int b) {return a - b;}public static int mul(int a, int b) {return a * b;}// 处理除数为零的情况public static int div(int a, int b) {if (b == 0) {throw new IllegalArgumentException("除数不能为零");}return a / b;}
}import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;// 测试类
public class MathTest {@Testpublic void testSum() {System.out.println("testSum");// 调用自定义的 MyMath 类的 sum 方法int actual = MyMath.sum(10, 20);int expected = 30;Assertions.assertEquals(expected, actual);}@Testpublic void testSub() {System.out.println("testSub");int actual = MyMath.sub(20, 10);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testMul() {System.out.println("testMul");int actual = MyMath.mul(2, 5);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testDiv() {System.out.println("testDiv");int actual = MyMath.div(20, 5);int expected = 4;Assertions.assertEquals(expected, actual);}@Testpublic void testDivByZero() {System.out.println("testDivByZero");Assertions.assertThrows(IllegalArgumentException.class, () -> {MyMath.div(10, 0);});}
}

运行结果:

3.自定义注解 Test

@Testable
public @interface Test {
}
1. @interface 关键字

在 Java 里,@interface 这个关键字是专门用来定义注解的。注解本质上是一种特殊的接口,它为代码元素(像类、方法、字段等)添加额外的元数据信息。当你使用 @interface 定义注解时,Java 编译器会自动将其编译成一个继承自 java.lang.annotation.Annotation 的接口。

2. public 访问修饰符

public 是访问修饰符,它表明这个注解可以在任何地方被访问和使用。如果不写 public,注解默认的访问权限是包级私有,也就是只能在同一个包内被访问。

3. @Testable 元注解

@Testable 是一个元注解,元注解的作用是修饰其他注解。在这里,@TestableTest 注解进行了标注,它为 Test 注解添加了额外的元数据或者功能。不过,@Testable 并非 Java 内置的元注解,它应该是自定义的。

五、 JUnit 5 常用注解


JUnit 5 提供了一些常见的测试生命周期注解。

5.1 运行前后的注解

 

示例

1.@BeforeAll  和@AfterAll 

MyMath类:

public class MyMath {public static int sum(int a, int b) {return a + b;}public static int sub(int a, int b) {return a - b;}public static int mul(int a, int b) {return a * b;}// 处理除数为零的情况public static int div(int a, int b) {if (b == 0) {throw new IllegalArgumentException("除数不能为零");}return a / b;}
}

MathTest类:

import org.junit.jupiter.api.*;// 测试类
public class MathTest {@BeforeAllpublic static void before(){System.out.println("所有测试方法执行前运行,仅运行一次(必须是静态方法)");System.out.println("开始执行单元测试了!");}@AfterAllpublic static void after(){System.out.println("所有测试方法执行后运行,仅运行一次(必须是静态方法)");System.out.println("单元测试执行完毕!");}@Testpublic void testSum() {System.out.println("testSum");// 调用自定义的 MyMath 类的 sum 方法int actual = MyMath.sum(10, 20);int expected = 30;Assertions.assertEquals(expected, actual);}@Testpublic void testSub() {System.out.println("testSub");int actual = MyMath.sub(20, 10);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testMul() {System.out.println("testMul");int actual = MyMath.mul(2, 5);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testDiv() {System.out.println("testDiv");int actual = MyMath.div(20, 5);int expected = 4;Assertions.assertEquals(expected, actual);}@Testpublic void testDivByZero() {System.out.println("testDivByZero");Assertions.assertThrows(IllegalArgumentException.class, () -> {MyMath.div(10, 0);});}
}

运行结果:

2.@BeforeEach和@AfterEach 

注意:@BeforeEach和@AfterEach 注解的方法不需要是静态的

 @BeforeEachpublic void beforeEach(){System.out.println("单元测试方法开始执行");}@AfterEachpublic void afterEach(){System.out.println("单元测试方法执行结束");}

示例代码:

 MyMath类:

public class MyMath {public static int sum(int a, int b) {return a + b;}public static int sub(int a, int b) {return a - b;}public static int mul(int a, int b) {return a * b;}// 处理除数为零的情况public static int div(int a, int b) {if (b == 0) {throw new IllegalArgumentException("除数不能为零");}return a / b;}
}

MathTest类:


import org.junit.jupiter.api.*;// 测试类
public class MathTest {@BeforeAllpublic static void before(){System.out.println("所有测试方法执行前运行,仅运行一次(必须是静态方法)");System.out.println("开始执行单元测试了!");}@AfterAllpublic static void after(){System.out.println("所有测试方法执行后运行,仅运行一次(必须是静态方法)");System.out.println("单元测试执行完毕!");}@BeforeEachpublic void beforeEach(){System.out.println("单元测试方法开始执行");}@AfterEachpublic void afterEach(){System.out.println("单元测试方法执行结束");}@Testpublic void testSum() {System.out.println("testSum");// 调用自定义的 MyMath 类的 sum 方法int actual = MyMath.sum(10, 20);int expected = 30;Assertions.assertEquals(expected, actual);}@Testpublic void testSub() {System.out.println("testSub");int actual = MyMath.sub(20, 10);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testMul() {System.out.println("testMul");int actual = MyMath.mul(2, 5);int expected = 10;Assertions.assertEquals(expected, actual);}@Testpublic void testDiv() {System.out.println("testDiv");int actual = MyMath.div(20, 5);int expected = 4;Assertions.assertEquals(expected, actual);}@Testpublic void testDivByZero() {System.out.println("testDivByZero");Assertions.assertThrows(IllegalArgumentException.class, () -> {MyMath.div(10, 0);});}
}

运行结果:

六、 解决单元测试中 Scanner 失效问题

上述 MyMath类:和MathTest类:加入下面代码

  @Testpublic void testAdd(){Scanner s=new Scanner(System.in);int i=s.nextInt();System.out.println("程序执行到这里了");}

运行结果:

程序会一直卡在那转圈

解决方法步骤

  1. 选中导航栏的 “Help”,然后选中 “Edit Custom VM Options...”: 在 IntelliJ IDEA 中,“Edit Custom VM Options...” 选项允许你对 Java 虚拟机(JVM)的运行参数进行自定义设置。通过这个操作,你可以打开用于存储自定义 JVM 选项的文件。

2.在 “IDEA64.exe.vmoptions” 文件中添加内容

-Deditable.java.test.console=true

-D是 JVM 的参数选项,用于设置系统属性。

editable.java.test.console=true这个属性设置的作用是开启可编辑的 Java 测试控制台。在默认情况下,单元测试的控制台可能不允许用户手动输入数据,而设置这个属性后,就可以在测试控制台中进行输入操作,从而让Scanner能够正常从标准输入读取数据。

3.重启 IDEA: 修改 JVM 选项后,需要重启 IDEA 才能使新的设置生效。重启后,再次运行包含Scanner的单元测试,应该就能正常读取输入了

七、其他高级功能


7.1 忽略测试


使用 @Disabled 让某个测试方法或测试类暂时不执行:

@Test
@Disabled("未完成的功能")
void testNotFinished() {System.out.println("这条测试不会执行");
}

7.2 超时测试


如果某个测试必须在指定时间内完成,可以使用 assertTimeout:

import static org.junit.jupiter.api.Assertions.*;
import java.time.Duration;@Test
void testTimeout() {assertTimeout(Duration.ofMillis(500), () -> Thread.sleep(300));
}


JUnit 5 是 Java 进行单元测试的标准框架,它提供了丰富的测试注解、断言方法和生命周期控制。
通过合理编写单元测试,可以显著提高代码质量,减少 Bug,增强代码的可维护性。
   


http://www.ppmy.cn/server/166973.html

相关文章

python之keyring库:安全密码管理库,不同平台service_name、username的获取

目录 keyring库的基本用法 设置及修改密码 获取密码 删除密码 检索密码 获取当前系统的所有service_name Windows 系统 macOS 系统 Linux 系统 跨平台封装 获取同一service_name下的所有username Windows 系统 macOS 系统 keyring 是一个Python库,它提供了一个简…

ProcessingP5js数据可视化

折线图绘制程序设计说明 可以读取表格数据&#xff0c;并转换成折线图&#xff0c;条形图和饼状图&#xff0c;并设计了衔接动画效果 1. 功能概述 本程序使用 Processing 读取 CSV 文件数据&#xff0c;并绘制带有坐标轴和数据点的折线图。横坐标&#xff08;X 轴&#xff09…

fps动作系统9:动画音频

文章目录 动画音频创建音频蓝图cue音量乘数 音效衰减衰减空间 绑定到动画动画序列轨道 动画音频 创建音频蓝图 cue 音量乘数 音量大小 音效衰减 空间音效 衰减 空间 绑定到动画 动画序列 轨道 横着的方向是有不同的轨道的&#xff0c;阴影的就是。

github - 使用

注册账户以及创建仓库 要想使用github第一步当然是注册github账号了, github官网地址:https://github.com/。 之后就可以创建仓库了(免费用户只能建公共仓库),Create a New Repository,填好名称后Create,之后会出现一些仓库的配置信息,这也是一个git的简单教程。 Git…

攻防世界33 catcat-new【文件包含/flask_session伪造】

题目&#xff1a; 点击一只猫猫&#xff1a; 看这个url像是文件包含漏洞&#xff0c;试试 dirsearch扫出来/admin&#xff0c;访问也没成功&#xff08;--delay 0.1 -t 5&#xff09; 会的那几招全用不了了哈哈&#xff0c;那就继续看答案 先总结几个知识点 1./etc/passwd&am…

antd-react日期组件disabledDate不可选择的日期 (置灰)属性

需求&#xff1a;原定结项时间表单里回显为2025-02-06&#xff0c;延期时间的选择范围要设置从2025-02-07开始选择包括2.7号的; 2.7号之前的置灰&#xff0c;不可选择 PC端部分代码&#xff1a; // react的函数组件写法 const disabledDate function (current) {console.log(c…

后盾人JS -- 异步编程,宏任务与微任务

异步加载图片体验JS任务操作 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title&g…

YOLOv11实战海洋动物图像识别

本文采用YOLOv11作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv11以其高效的特征提取能力&#xff0c;在多个图像分类任务中展现出卓越性能。本研究针对5种海洋动物数据集进行训练和优化&#xff0c;该数据集包含丰富的海洋动物图像…