1852 字
约 6 分钟
9
Java单元测试
2026-09-19
无标签
第66章 Java单元测试
面包屑导航 章节头部 学习目标
学习目标
- 掌握JUnit 5测试框架的基本概念和核心特性
- 学会使用各种测试注解(@Test、@BeforeEach、@AfterEach等)
- 理解断言方法的使用和测试验证技巧
- 掌握参数化测试、重复测试和条件测试
- 学会异常测试和超时测试的编写
- 了解测试生命周期和测试组织结构
- 掌握单元测试的最佳实践和代码覆盖率
JUnit 5 核心特性
JUnit 5 核心特性详解
JUnit 5是Java生态系统中最流行的单元测试框架,它提供了丰富的注解、断言方法和扩展机制。JUnit 5由三个子项目组成:JUnit Platform、JUnit Jupiter和JUnit Vintage,为现代Java应用提供了强大的测试支持。
测试注解
@Test
void testMethod() {
// 测试方法
}
@BeforeEach
void setUp() {
// 每个测试前执行
}
- @Test - 标记测试方法
- @BeforeEach - 每个测试前执行
- @AfterEach - 每个测试后执行
- @DisplayName - 自定义显示名称
断言方法
assertEquals(expected, actual);
assertTrue(condition);
assertThrows(Exception.class, () -> {
// 可能抛出异常的代码
});
- assertEquals - 相等断言
- assertTrue/False - 布尔断言
- assertThrows - 异常断言
- assertAll - 组合断言
参数化测试
@ParameterizedTest
@ValueSource(strings = {"hello", "world"})
void testWithParameter(String argument) {
assertNotNull(argument);
}
- @ValueSource - 简单值源
- @CsvSource - CSV数据源
- @MethodSource - 方法数据源
- @EnumSource - 枚举数据源
测试注解详解
JUnit 5 测试注解详解
JUnit 5提供了丰富的注解来控制测试的执行流程和行为,这些注解能够帮助我们构建结构化和可维护的测试代码。
| 注解 | 说明 | 使用场景 |
|---|---|---|
| @Test | 标记测试方法 | 基本测试方法 |
| @BeforeEach | 每个测试前执行 | 测试数据准备 |
| @AfterEach | 每个测试后执行 | 资源清理 |
| @BeforeAll | 所有测试前执行一次 | 类级别初始化 |
| @AfterAll | 所有测试后执行一次 | 类级别清理 |
| @DisplayName | 自定义测试显示名称 | 提高可读性 |
| @Disabled | 禁用测试 | 临时跳过测试 |
| @ParameterizedTest | 参数化测试 | 多组数据测试 |
| @RepeatedTest | 重复测试 | 压力测试 |
| @Timeout | 超时测试 | 性能测试 |
完整代码示例
完整测试代码示例
以下是一个完整的JUnit 5测试示例,展示了各种测试注解、断言方法和测试技巧的使用。
/**
* 计算器类 - 用于演示单元测试
*
* 这个类提供基本的数学运算功能
* 包括加法、减法、乘法、除法等操作
*/
public class Calculator {
/**
* 加法运算
*/
public int add(int a, int b) {
return a + b;
}
/**
* 除法运算
*/
public double divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return (double) a / b;
}
/**
* 计算阶乘
*/
public long factorial(int n) {
if (n < 0) {
throw new IllegalArgumentException("阶乘的参数不能为负数");
}
if (n == 0 || n == 1) {
return 1;
}
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* Calculator类的单元测试
* 演示JUnit 5的各种测试注解和断言方法
*/
class CalculatorTest {
private Calculator calculator;
@BeforeAll
static void setUpClass() {
System.out.println("开始执行Calculator测试类");
}
@BeforeEach
void setUp() {
calculator = new Calculator();
System.out.println("创建Calculator实例");
}
@Test
@DisplayName("测试加法运算")
void testAdd() {
// 测试正数相加
assertEquals(5, calculator.add(2, 3));
assertEquals(0, calculator.add(-2, 2));
assertEquals(-5, calculator.add(-2, -3));
// 测试边界值
assertEquals(1, calculator.add(0, 1));
assertEquals(0, calculator.add(0, 0));
}
@Test
@DisplayName("测试除法运算")
void testDivide() {
assertEquals(2.0, calculator.divide(6, 3), 0.001);
assertEquals(2.5, calculator.divide(5, 2), 0.001);
assertEquals(-2.0, calculator.divide(-6, 3), 0.001);
}
@Test
@DisplayName("测试除零异常")
void testDivideByZero() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> calculator.divide(5, 0)
);
assertEquals("除数不能为0", exception.getMessage());
}
@ParameterizedTest
@DisplayName("参数化测试加法")
@CsvSource({
"1, 2, 3",
"0, 0, 0",
"-1, 1, 0",
"10, -5, 5"
})
void testAddParameterized(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
@RepeatedTest(5)
@DisplayName("重复测试加法")
void testAddRepeated() {
assertEquals(4, calculator.add(2, 2));
}
@Test
@Timeout(1)
@DisplayName("超时测试")
void testTimeout() {
assertEquals(100, calculator.add(50, 50));
}
@Nested
@DisplayName("数学运算测试组")
class MathOperationsTest {
@Test
@DisplayName("组合断言测试")
void testCombinedAssertions() {
assertAll("基本运算",
() -> assertEquals(5, calculator.add(2, 3)),
() -> assertEquals(6, calculator.multiply(2, 3)),
() -> assertEquals(2.0, calculator.divide(6, 3))
);
}
}
}
💻 查看完整代码 - 在线IDE体验 测试最佳实践
单元测试最佳实践
测试编写原则
好的测试实践
@Test
@DisplayName("当输入两个正数时应该返回正确的和")
void shouldReturnSumWhenAddingTwoPositiveNumbers() {
// Arrange - 准备测试数据
Calculator calculator = new Calculator();
// Act - 执行被测试的方法
int result = calculator.add(2, 3);
// Assert - 验证结果
assertEquals(5, result);
}
- 测试方法命名清晰描述性
- 遵循AAA模式(Arrange-Act-Assert)
- 测试边界条件和异常情况
- 使用@DisplayName提高可读性
- 每个测试只验证一个功能点
应该避免的实践
@Test
void test1() {
// 测试多个不相关的功能
assertEquals(5, calculator.add(2, 3));
assertEquals(6, calculator.multiply(2, 3));
assertEquals("hello", stringUtils.reverse("olleh"));
}
- 测试方法名称不够描述性
- 一个测试方法测试多个功能
- 测试之间存在依赖关系
- 忽略边界条件和异常情况
- 测试代码过于复杂
测试类型和策略
测试类型和策略
常用测试类型基本功能测试:验证方法的基本功能是否正确边界值测试:测试输入参数的边界情况异常测试:验证异常情况的处理是否正确参数化测试:使用多组数据验证同一功能性能测试:验证方法的执行时间是否符合要求
测试编写注意事项测试应该独立运行,不依赖其他测试的结果测试数据应该在测试方法内部准备,避免全局状态使用有意义的断言消息,便于问题定位及时清理测试过程中创建的资源保持测试代码的简洁性和可读性
测试覆盖率目标
- 代码覆盖率应该达到80%以上
- 重点关注核心业务逻辑的测试覆盖
- 确保所有公共方法都有对应的测试
- 异常分支和边界条件也要有相应测试
- 使用JaCoCo等工具监控测试覆盖率
本章小结
本章小结
- JUnit 5是Java生态系统中最流行和强大的单元测试框架
- 掌握了@Test、@BeforeEach、@AfterEach等核心测试注解的使用
- 学会了assertEquals、assertTrue、assertThrows等断言方法
- 了解了参数化测试、重复测试和条件测试的编写方法
- 掌握了异常测试和超时测试的实现技巧
- 理解了测试生命周期和嵌套测试的组织结构
- 学会了单元测试的最佳实践和代码覆盖率要求
- 能够为Java项目编写高质量的单元测试代码
评论
0 条
还没有评论,先写一条吧。