75、单元测试-嵌套测试
**嵌套测试**是JUnit 5提供的一种功能,允许将测试类组织成层次结构,提升测试代码的可读性和可维护性。
### 什么是嵌套测试?
嵌套测试通过`@Nested`注解,将测试类定义为外部类的**内部类**,形成嵌套结构。每个嵌套类可以包含自己的测试方法,以及`@BeforeEach`、`@AfterEach`等生命周期方法。
### 优点
1. **逻辑分组**:将相关的测试用例按功能或状态分组,使代码结构更清晰。
2. **共享上下文**:内部类可以访问外部类的成员变量和方法,方便共享测试数据和设置。
3. **独立生命周期**:每个嵌套类可以有独立的初始化和清理方法,减少重复代码。
### 使用示例
```java
import org.junit.jupiter.api.*;
public class ShoppingCartTest {
private ShoppingCart cart;
@BeforeEach
void init() {
cart = new ShoppingCart();
}
@Nested
@DisplayName("空购物车")
class EmptyCart {
@Test
@DisplayName("总金额应为0")
void totalPriceShouldBeZero() {
assertEquals(0, cart.getTotalPrice());
}
@Test
@DisplayName("移除商品应抛出异常")
void removeItemShouldThrowException() {
assertThrows(IllegalStateException.class, () -> cart.removeItem("apple"));
}
}
@Nested
@DisplayName("添加商品后")
class WithItems {
@BeforeEach
void addItem() {
cart.addItem("apple", 2);
cart.addItem("banana", 3);
}
@Test
@DisplayName("总金额应正确")
void totalPriceShouldBeCorrect() {
assertEquals(2 * 5 + 3 * 3, cart.getTotalPrice());
}
@Nested
@DisplayName("移除商品")
class AfterRemovingItem {
@Test
@DisplayName("移除存在的商品")
void removeExistingItem() {
cart.removeItem("apple");
assertEquals(3, cart.getItemCount());
}
@Test
@DisplayName("移除不存在的商品应抛出异常")
void removeNonExistingItem() {
assertThrows(IllegalArgumentException.class, () -> cart.removeItem("orange"));
}
}
}
}
```
### 注意事项
- **非静态内部类**:`@Nested`类必须是非静态的,以便访问外部类的成员。
- **执行顺序**:嵌套测试的执行顺序是从外到内,但同层级的嵌套类之间顺序不确定。
- **嵌套层级**:建议嵌套层级不超过3层,过深的嵌套会降低代码可读性。
- **生命周期方法**:嵌套类可以有自己的`@BeforeEach`和`@AfterEach`方法,但不会继承外部类的这些方法。
### 适用场景
- **复杂对象的状态测试**:例如,购物车的空状态、添加商品状态、结算状态等。
- **分层测试逻辑**:将初始化、操作、验证等步骤分到不同的嵌套类中。
- **多配置组合测试**:测试不同配置下的对象行为。
通过合理使用嵌套测试,可以有效地组织和管理测试代码,提高单元测试的可读性和维护性。