JUnit5 单元测试详解

devtools/2025/2/12 18:50:50/

目录

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

二、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/devtools/158284.html

相关文章

【系统设计】Spring、SpringMVC 与 Spring Boot 技术选型指南:人群、场景与实战建议

在 Java 开发领域&#xff0c;Spring 生态的技术选型直接影响项目的开发效率、维护成本和长期扩展性。然而&#xff0c;面对 Spring、SpringMVC 和 Spring Boot 这三个紧密关联的框架&#xff0c;开发者常常陷入纠结&#xff1a;该从何入手&#xff1f;如何根据团队能力和业务需…

Win11下搭建Kafka环境

目录 一、环境准备 二、安装JDK 1、下载JDK 2、配置环境变量 3、验证 三、安装zookeeper 1、下载Zookeeper安装包 2、配置环境变量 3、修改配置文件zoo.cfg 4、启动Zookeeper服务 4.1 启动Zookeeper客户端验证 4.2 启动客户端 四、安装Kafka 1、下载Kafka安装包…

CSS Position宝典:解锁网页元素精准布局的秘密武器

在网页设计的浩瀚宇宙中&#xff0c;CSS Position属性无疑是那把开启精准布局大门的钥匙。它如同一位技艺高超的魔术师&#xff0c;让网页元素在屏幕上自由穿梭&#xff0c;无论是固定位置的导航栏、悬浮的提示框&#xff0c;还是动态变化的弹出层&#xff0c;都离不开Position…

JVM常见命令

引言 掌握JVM是属于Java程序员的必修课&#xff0c;对线程的掌控&#xff0c;对内存的把控&#xff0c;所以了解JVM常见命令可以帮助我们快速了解虚拟机的详细数据 命令 1. java 这是最基础的命令&#xff0c;用于启动一个 Java 应用程序。 java -cp /path/to/your/class…

【Elasticsearch】bucket_sort

Elasticsearch 的bucket_sort聚合是一种管道聚合&#xff0c;用于对父多桶聚合&#xff08;如terms、date_histogram、histogram等&#xff09;的桶进行排序。以下是关于bucket_sort的详细说明&#xff1a; 1.基本功能 bucket_sort聚合可以对父聚合返回的桶进行排序&#xff…

蓝桥杯 Java B 组之函数定义与递归入门

一、Java 函数&#xff08;方法&#xff09;基础 1. 什么是函数&#xff1f; 函数&#xff08;方法&#xff09;是 一段可复用的代码块&#xff0c;通过 函数调用 执行&#xff0c;并可返回值。在 Java 里&#xff0c;函数也被叫做方法&#xff0c;它是一段具有特定功能的、可…

ESP32S3基于espidf ADC使用

ESP32S3基于espidf ADC使用 官方在线文档介绍模数转换器&#xff1a;https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32s3/api-reference/peripherals/adc_oneshot.html&#x1f516;espidf版本&#xff1a;v5.4 模数转换器 (ADC)转换方式&#xff1a; 模数转换…

详解Redis中lua脚本和事务

In learning knowledge, one should be good at thinking, thinking, and thinking again. —-Albert Einstein 引言 Lua脚本的原子性和事务的ACID特性想必大家都很熟悉&#xff0c;本篇文章将从性能表现和原理帮助我们快速理解他们 基本概念 1. Redis Lua 脚本 从 2.6 版本…