JavaWeb从0到1-DAY5.1-Maven-JUnit

JUnit单元测试学习笔记

一、这一章在讲什么

这一章主要学习 JUnit 单元测试。单元测试就是针对一个最小功能单元,通常是一个方法,编写测试代码来验证它是否按预期工作。以前可以用 main 方法手动测试,但这种方式测试代码和业务代码混在一起,不方便维护,也不利于自动化。JUnit 可以把测试代码和源代码分开,并通过断言自动判断测试是否成功。结合 CalculatorTest.java 来看,重点是理解 @Test、断言、异常测试、批量断言、测试目录规范和 Maven 中的 scope=test

二、核心概念

1. 单元测试

  • 它是什么

    • 单元测试就是针对最小的功能单元进行测试。
    • 在 Java 中,最小功能单元通常就是一个方法。
  • 有什么作用

    • 检查方法的执行结果是否符合预期;
    • 提前发现代码错误;
    • 后期修改代码时,可以快速验证有没有把旧功能改坏。
  • 它的原理

    • 写一个专门的测试方法;
    • 在测试方法中调用被测试的代码;
    • 用断言判断实际结果和预期结果是否一致。
  • 初学者容易混淆的点

    • 单元测试不是业务代码;
    • 单元测试不是简单打印结果;
    • System.out.println() 只能让人看,断言才能让测试框架自动判断对错。

2. JUnit

  • 它是什么
    • JUnit 是 Java 中常用的单元测试框架。
    • 你的 CalculatorTest.java 用的是 JUnit 5,因为导入的是:
java 复制代码
import org.junit.jupiter.api.Test;
  • 有什么作用

    • 让测试方法不需要 main 方法也能运行;
    • 可以自动执行多个测试方法;
    • 可以自动判断测试成功或失败;
    • 可以生成测试结果,成功通常显示绿色,失败通常显示红色。
  • 它的原理

    • JUnit 测试引擎会扫描测试类;
    • 找到带有 @Test 等测试注解的方法;
    • 自动调用这些方法;
    • 根据断言结果判断测试是否通过。
  • 初学者容易混淆的点

    • 不是方法名叫 test 就一定会执行;
    • 关键是方法上要有 @Test 注解;
    • JUnit 执行测试方法,不依赖自己写 main 方法。

3. @Test 注解

  • 它是什么

    • @Test 是 JUnit 中用来标记测试方法的注解。
  • 有什么作用

    • 告诉 JUnit:这个方法是测试方法,可以被自动执行。
  • 它的原理

    • JUnit 运行时会识别带有 @Test 的方法;
    • 然后通过测试引擎调用这些方法。
  • 初学者容易混淆的点

    • 去掉 @Test 后,这个方法就只是普通方法;
    • 普通方法不会被 JUnit 自动当成测试执行。

4. 断言

  • 它是什么

    • 断言就是用代码写出"我期望的结果",让 JUnit 判断实际结果是否符合预期。
  • 有什么作用

    • 自动判断测试成功或失败;
    • 替代人工看控制台输出;
    • 让测试结果更明确。
  • 它的原理

    • 如果断言条件满足,测试继续执行;
    • 如果断言条件不满足,测试失败,JUnit 会报告错误信息。
  • 初学者容易混淆的点

    • 断言失败时,不是"执行第三个参数";
    • 第三个参数通常是失败时显示的提示信息;
    • 比如 assertEquals(5, result, "2+3 应该等于 5") 中,第三个参数只有在测试失败时才作为错误提示显示。

5. Maven 中的 scope=test

  • 它是什么

    • scope=test 表示这个依赖只在测试范围内生效。
  • 有什么作用

    • JUnit 是测试框架,只应该给测试代码使用;
    • 正式业务代码不应该依赖 JUnit。
  • 它的原理

    • src/test/java 下的测试代码可以使用这个依赖;
    • src/main/java 下的主程序代码不应该使用这个依赖;
    • 打正式运行包时,这类测试依赖通常不会作为运行依赖参与进去。
  • 初学者容易混淆的点

    • scope=test 不是"让源代码中也执行测试";
    • 它是限制 JUnit 只在测试阶段可用,避免污染主程序。

三、重难点

1. 为什么 JUnit 测试不需要 main 方法

  • 结论

    • 因为 JUnit 自己有测试引擎,会自动找到并执行测试方法。
  • 原因

    • 我们平时写普通 Java 程序,需要 main 方法作为入口;
    • 但测试代码是由 JUnit 框架启动的;
    • JUnit 会扫描带 @Test 的方法,把它们当作测试入口。
  • 比喻

    • 普通程序像自己开门进房间,main 是门;
    • JUnit 测试像老师点名,谁身上有 @Test 标记,老师就叫谁出来答题。

2. @Test 不能随便去掉

  • 结论

    • 测试方法必须用 @Test 标记,JUnit 才会识别它。
  • 原因

    • JUnit 判断一个方法是不是测试方法,主要看注解;
    • 不是只看方法名。
  • 例子

java 复制代码
@Test
void testAddition() {
    int result = 2 + 3;
    assertEquals(5, result);
}

如果去掉 @Test

java 复制代码
void testAddition() {
    int result = 2 + 3;
    assertEquals(5, result);
}

这个方法就只是普通方法,不会被 JUnit 自动执行。


3. assertEquals 的三个参数

  • 结论

    • assertEquals(expected, actual, message) 用来判断实际值是否等于期望值。
  • 原因

    • 单元测试最核心的思想就是:给定输入,检查输出是否符合预期。
  • 例子

java 复制代码
assertEquals(5, result, "2+3 应该等于 5");

含义:

  • 5:期望值;
  • result:实际值;
  • "2+3 应该等于 5":失败时显示的提示信息。

注意:

第三个参数不是"要执行的代码",而是测试失败时的说明文字。


4. assertThrows 是专门测试异常的

  • 结论

    • assertThrows 用来判断某段代码是否会抛出指定类型的异常。
  • 原因

    • 有些方法在遇到非法数据时,本来就应该抛异常;
    • 这时候"抛出正确异常"反而说明代码是对的。
  • 例子

java 复制代码
assertThrows(ArithmeticException.class, () -> {
    int x = 1 / 0;
});

这段测试的意思是:

我预期这段代码会抛出 ArithmeticException

因为整数 1 / 0 确实会抛出 ArithmeticException,所以测试通过。

如果这段代码没有抛异常,或者抛出的不是 ArithmeticException,测试才会失败。

  • 比喻
    • 普通断言是在检查"答案是不是对";
    • assertThrows 是在检查"该报错的时候有没有报正确的错"。

5. assertAll 是批量断言

  • 结论

    • assertAll 可以把多个断言组合成一组一起执行。
  • 原因

    • 普通多个断言顺序执行时,如果前面的断言失败,后面的断言可能不再执行;
    • assertAll 会尽量执行组内所有断言,并汇总失败信息。
  • 例子

java 复制代码
assertAll("user",
    () -> assertEquals("Alice", getUserName()),
    () -> assertNotNull(getUserAge())
);

含义:

  • 这一组断言的名字叫 user

  • 第一个断言检查用户名是不是 Alice

  • 第二个断言检查年龄是不是不为 null

  • 比喻

    • 普通断言像检查作业时发现第一题错了就停下;
    • assertAll 像把整张卷子都检查完,再告诉你哪些题错了。

6. 测试代码应该放在 src/test/java

  • 结论

    • 测试类应该放在 src/test/java,业务代码放在 src/main/java
  • 原因

    • 这是 Maven 的标准项目结构;
    • Maven 会把主程序和测试程序分开处理;
    • 测试代码不会混进正式业务代码里。
  • 例子

text 复制代码
src/main/java      放正式代码
src/test/java      放测试代码
  • 比喻
    • src/main/java 是正式作业;
    • src/test/java 是检查作业用的验算纸。

7. 企业开发中要测边界值

  • 结论

    • 单元测试不能只测正常情况,还要测异常情况和边界情况。
  • 原因

    • 很多 bug 都出现在特殊输入上,比如 null、空字符串、长度不对、极大值、极小值。
  • 例子

    • 如果测试加法方法,不能只测:
java 复制代码
2 + 3 = 5
  • 还可以测:
text 复制代码
0 + 0
-1 + 1
-2 + -3
Integer.MAX_VALUE + 1
Integer.MIN_VALUE - 1

注意:

如果方法参数是 int,就不能传入字符或字符串;字符、字符串适合测试参数类型为 String 的方法。

四、代码理解

1. CalculatorTest.java 整体结构

java 复制代码
package com.test;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    @Test
    void testAddition() {
        int result = 2 + 3;
        assertEquals(5, result, "2+3 应该等于 5");
    }

    @Test
    void testDivideByZero() {
        assertThrows(ArithmeticException.class, () -> {
            int x = 1 / 0;
        });
    }

    @Test
    void testMultipleAssertions() {
        assertAll("user",
            () -> assertEquals("Alice", getUserName()),
            () -> assertNotNull(getUserAge())
        );
    }

    private String getUserName() { return "Alice"; }
    private Integer getUserAge() { return 25; }
}

关键行理解:

  • import org.junit.jupiter.api.Test;

    • 导入 JUnit 5 的 @Test 注解。
  • import static org.junit.jupiter.api.Assertions.*;

    • 静态导入断言方法;
    • 这样就可以直接写 assertEquals(),不用写 Assertions.assertEquals()
  • @Test

    • 标记下面的方法是测试方法。
  • assertEquals(5, result, "2+3 应该等于 5")

    • 判断 result 是否等于 5
    • 不等于就测试失败,并显示提示信息。
  • assertThrows(ArithmeticException.class, () -> { int x = 1 / 0; })

    • 判断代码块是否抛出算术异常;
    • 抛出了指定异常,测试通过。
  • assertAll(...)

    • 把多个断言合成一组执行。

2. 常见断言方法

java 复制代码
assertEquals(期望值, 实际值, 失败提示);
assertNotEquals(不期望的值, 实际值, 失败提示);
assertNull(实际对象, 失败提示);
assertNotNull(实际对象, 失败提示);
assertTrue(条件, 失败提示);
assertFalse(条件, 失败提示);
assertThrows(异常类型.class, 要执行的代码, 失败提示);

记忆重点:

  • Equals:判断相等;
  • NotEquals:判断不相等;
  • Null:判断为 null
  • NotNull:判断不为 null
  • True:判断条件为真;
  • False:判断条件为假;
  • Throws:判断会不会抛出指定异常。

3. 常见注解

java 复制代码
@Test

标记普通测试方法。

java 复制代码
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})

参数化测试,让同一个测试方法用多组参数运行。

java 复制代码
@BeforeEach

每个测试方法执行前运行一次。

java 复制代码
@AfterEach

每个测试方法执行后运行一次。

java 复制代码
@BeforeAll

所有测试方法执行前只运行一次,通常用于全局初始化。

java 复制代码
@AfterAll

所有测试方法执行后只运行一次,通常用于全局清理。


4. 参数化测试示例

java 复制代码
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testPositiveNumber(int number) {
    assertTrue(number > 0);
}

这段代码会执行三次:

text 复制代码
number = 1
number = 2
number = 3

注意:

使用 @ParameterizedTest 时,不需要再加 @Test


5. 测试前后执行的方法

java 复制代码
@BeforeEach
void setUp() {
    System.out.println("每个测试方法前执行");
}

@AfterEach
void tearDown() {
    System.out.println("每个测试方法后执行");
}

如果一个测试类中有 3 个 @Test 方法:

  • @BeforeEach 会执行 3 次;
  • @AfterEach 会执行 3 次。
java 复制代码
@BeforeAll
static void beforeAll() {
    System.out.println("所有测试前只执行一次");
}

@AfterAll
static void afterAll() {
    System.out.println("所有测试后只执行一次");
}

如果一个测试类中有 3 个 @Test 方法:

  • @BeforeAll 只执行 1 次;
  • @AfterAll 只执行 1 次。

五、易错点

  1. 以为 JUnit 测试必须写 main 方法

    • JUnit 测试不需要自己写 main
    • JUnit 测试引擎会自动执行带 @Test 的方法。
  2. 去掉 @Test 导致方法不执行

    • 方法名叫 testAddition 不够;
    • 必须加 @Test 才能被 JUnit 识别。
  3. 误解 assertEquals 的第三个参数

    • 第三个参数是失败提示信息;
    • 不是实际值不等于期望值时要执行的代码。
  4. 不理解 assertThrows 为什么通过

    • 如果预期某段代码应该抛异常,并且它确实抛出了指定异常,测试就是通过。
  5. 不理解 assertAll 的作用

    • assertAll 是把多个断言组合起来,尽量全部执行后统一汇总结果。
  6. 把测试代码放到 src/main/java

    • 测试代码应该放在 src/test/java
    • 业务代码应该放在 src/main/java
  7. JUnit 依赖没有设置 scope=test

    • JUnit 是测试依赖,不应该成为主程序运行依赖。
  8. 只测试正常情况,不测试边界情况

    • 企业开发中,null、空值、边界值、异常值都很重要。

六、记忆口诀 / 通俗比喻

1. 单元测试口诀

一个方法一个测,结果对错断言说。

2. JUnit 口诀

@Test 一贴,JUnit 来测。

3. 断言口诀

期望在前,实际在后,错了提示跟最后。

4. assertThrows 口诀

该报错时报对错,就是通过。

5. assertAll 比喻

普通断言像错一题就停;assertAll 像整张卷子都批完再汇总。

6. Maven 测试目录比喻

main 放正式作业,test 放验算草稿。

七、应用

在实际开发中,单元测试主要用来保证方法的正确性。比如写一个用户注册功能,不能只测试正常注册成功,还要测试用户名为空、密码为空、手机号格式错误、用户已存在等情况。写完业务方法后,把测试类放到 src/test/java,用 @Test 标记测试方法,用 assertEqualsassertTrueassertThrows 等断言检查结果。如果修改了业务代码,再运行测试,就能快速知道有没有把原来的功能改坏。JUnit 配合 Maven 的 scope=test 使用,可以让测试代码和正式业务代码分开,项目结构更清楚,也更符合企业开发规范。

八、最终总结

JUnit 是 Java 中常用的单元测试框架,用来测试方法是否按预期工作。测试方法不需要 main,只要加上 @Test,JUnit 测试引擎就能自动识别并执行。断言是单元测试的核心,assertEquals 判断结果是否相等,assertThrows 判断是否抛出指定异常,assertAll 可以批量执行多个断言。测试代码应该放在 src/test/java,JUnit 依赖应该设置 scope=test。企业开发中写测试不能只测正常情况,还要覆盖异常值和边界值。

相关推荐
ppandss11 小时前
JavaWeb从0到1-DAY5-Maven
python·maven
农业工作者1 小时前
IDEA解决springboot工程中Cannot resolve symbol ‘SpringApplication异常 maven解决
java·开发语言·maven
上海合宙LuatOS2 小时前
Air780EPM通过MQTT上传温湿度数据
开发语言·人工智能·物联网·junit·luatos
0和1的搬运工20 小时前
基于Java+SpringBoot+Vue+HTML5高校教师电子名片系统(源码+LW+调试文档+讲解等)/高校教师/电子名片/系统/教育科技/教育信息化/名片管理/电子身份/教师信息管理/校园信息化
spring cloud·tomcat·log4j·maven·intellij-idea·dubbo·java-consul
铁皮哥1 天前
【后端开发】@Resource 和 @Autowired 到底有什么区别?为什么现在更推荐构造方法注入?
java·ide·spring boot·tomcat·log4j·idea·intellij idea
devilnumber1 天前
maven依赖的直接下载jar
java·maven
铁皮哥2 天前
【后端/Agent 开发】给你的项目配置一套 .claude/ 工作流:别再裸用 Claude Code 了!
java·windows·python·spring·github·maven·生活
摇滚侠2 天前
Unsupported class file major version 61
java·maven
abcnull3 天前
Springboot+Vue2的Web项目小白入门Demo快速学习!
java·elementui·vue·maven·springboot·web·小白