当前位置: 首页 > article >正文

JUnit5 单元测试详解

目录

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

二、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;
    }

    // 单元测试方法
    @Test
    void 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 {
    @Test
    public void testSum() {
        System.out.println("testSum");
        // 调用自定义的 MyMath 类的 sum 方法
        int actual = MyMath.sum(10, 20);
        int expected = 30;
        Assertions.assertEquals(expected, actual);
    }

    @Test
    public void testSub() {
        System.out.println("testSub");
        int actual = MyMath.sub(20, 10);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }

    @Test
    public void testMul() {
        System.out.println("testMul");
        int actual = MyMath.mul(2, 5);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }

    @Test
    public void testDiv() {
        System.out.println("testDiv");
        int actual = MyMath.div(20, 5);
        int expected = 4;
        Assertions.assertEquals(expected, actual);
    }

    @Test
    public 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 {
    @BeforeAll
    public static void before(){
        System.out.println("所有测试方法执行前运行,仅运行一次(必须是静态方法)");
        System.out.println("开始执行单元测试了!");

    }

    @AfterAll
    public static void after(){
        System.out.println("所有测试方法执行后运行,仅运行一次(必须是静态方法)");
        System.out.println("单元测试执行完毕!");
    }




    @Test
    public void testSum() {
        System.out.println("testSum");
        // 调用自定义的 MyMath 类的 sum 方法
        int actual = MyMath.sum(10, 20);
        int expected = 30;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testSub() {
        System.out.println("testSub");
        int actual = MyMath.sub(20, 10);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testMul() {
        System.out.println("testMul");
        int actual = MyMath.mul(2, 5);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testDiv() {
        System.out.println("testDiv");
        int actual = MyMath.div(20, 5);
        int expected = 4;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testDivByZero() {
        System.out.println("testDivByZero");
        Assertions.assertThrows(IllegalArgumentException.class, () -> {
            MyMath.div(10, 0);
        });
    }
}

运行结果:

2.@BeforeEach和@AfterEach 

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

 @BeforeEach
    public void beforeEach(){
        System.out.println("单元测试方法开始执行");
    }

    @AfterEach
    public 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 {
    @BeforeAll
    public static void before(){
        System.out.println("所有测试方法执行前运行,仅运行一次(必须是静态方法)");
        System.out.println("开始执行单元测试了!");

    }

    @AfterAll
    public static void after(){
        System.out.println("所有测试方法执行后运行,仅运行一次(必须是静态方法)");
        System.out.println("单元测试执行完毕!");
    }

    @BeforeEach
    public void beforeEach(){
        System.out.println("单元测试方法开始执行");
    }

    @AfterEach
    public void afterEach(){
        System.out.println("单元测试方法执行结束");
    }




    @Test
    public void testSum() {
        System.out.println("testSum");
        // 调用自定义的 MyMath 类的 sum 方法
        int actual = MyMath.sum(10, 20);
        int expected = 30;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testSub() {
        System.out.println("testSub");
        int actual = MyMath.sub(20, 10);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testMul() {
        System.out.println("testMul");
        int actual = MyMath.mul(2, 5);
        int expected = 10;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testDiv() {
        System.out.println("testDiv");
        int actual = MyMath.div(20, 5);
        int expected = 4;
        Assertions.assertEquals(expected, actual);
    }
    @Test
    public void testDivByZero() {
        System.out.println("testDivByZero");
        Assertions.assertThrows(IllegalArgumentException.class, () -> {
            MyMath.div(10, 0);
        });
    }
}

运行结果:

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

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

  @Test
    public 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.kler.cn/a/541859.html

相关文章:

  • 嵌入式硬件篇---原码、补码、反码
  • Docker 在 Java 开发中的实践与应用:解锁高效容器化部署新姿势
  • 怎么查看电脑显存大小(查看电脑配置)
  • Linux 安装 Ollama
  • C语言学习笔记:子函数的调用实现各个位的累加和
  • 2025web寒假作业二
  • centos 7 关于引用stdatomic.h的问题
  • 【Git】完美解决git push报错403
  • 【20250211】栈与队列:225.用队列实现栈
  • 采集学校食堂人脸识别证件照,且尺寸底色统一的方法
  • 数据驱动企业数据智能化发展-通过财务数据分析模型评估企业经营健康度
  • ListWise 排序技术综述:从传统领域到大模型领域的跨越
  • APP广告变现,对接聚合广告平台创建广告位流程
  • dpdk的基础使用-抓包
  • RESTful开发中对象的合理使用探究
  • 分布式服务框架 如何设计一个更合理的协议
  • 爬取彩票网站数据
  • rpx和px混用方案
  • 【2024最新Java面试宝典】—— SpringBoot面试题(44道含答案)_java spingboot 面试题
  • el-table多列勾选
  • Vue2生命周期面试题
  • Access数据库教案(Excel+VBA+Access数据库SQL Server编程)
  • (3/100)每日小游戏平台系列
  • Visual Studio 2022环境下Miracl Lib库报错“无法解析的外部命令”
  • 数字孪生平台 v5.2 发布
  • Vulnhub empire-lupinone靶机攻击实战(一)