本文目录导读:

测试金字塔与分层设计
单元测试(Unit Test)
- 目标:测试最小代码单元(方法、类)
- 工具:JUnit 5, Mockito, AssertJ
- 特点:快速、隔离、无外部依赖
集成测试(Integration Test)
- 目标:验证模块间交互(数据库、API、消息队列)
- 工具:Testcontainers, Spring Boot Test, H2
- 特点:需要外部资源,验证边界
端到端测试(E2E Test)
- 目标:模拟用户真实操作
- 工具:Selenium, Cypress, REST Assured
- 特点:最慢但最接近真实场景
测试用例设计方法
等价类划分法
将输入数据划分为有效等价类和无效等价类,每个类取一个代表值。
示例:年龄字段(0-150岁)
// 有效等价类
@Test
void validAgeRange() {
assertDoesNotThrow(() -> new User(25));
}
// 无效等价类
@Test
void ageTooLow() {
assertThrows(IllegalArgumentException.class, () -> new User(-1));
}
@Test
void ageTooHigh() {
assertThrows(IllegalArgumentException.class, () -> new User(200));
}
边界值分析法
关注等价类的边界值(最小值、最大值、略小于/大于边界)。
示例:密码长度(8-20位)
// 边界值:7, 8, 20, 21
@Test
void passwordLengthBoundary() {
assertThrows(IllegalArgumentException.class, () -> new Password("1234567"));
assertDoesNotThrow(() -> new Password("12345678"));
assertDoesNotThrow(() -> new Password("12345678901234567890"));
assertThrows(IllegalArgumentException.class, () -> new Password("123456789012345678901"));
}
因果图法
用因果关系图描述输入条件间的组合,适合复杂业务逻辑。
示例:优惠券系统(新用户 + 订单满100)→ 可用
@Test
void couponApplicableWhenNewUserAndOrderOver100() {
User user = new User(true, false);
Order order = new Order(new BigDecimal("150"));
assertTrue(CouponService.isApplicable(user, order));
}
@Test
void couponNotApplicableWhenExistingUser() {
User user = new User(false, false);
Order order = new Order(new BigDecimal("150"));
assertFalse(CouponService.isApplicable(user, order));
}
正交实验法
用正交表减少全组合测试用例数,适合多条件场景。
示例:搜索功能(关键词、排序、过滤)
// 全组合:3x2x2=12,正交表可选4个
@ParameterizedTest
@CsvSource({
"Java, price_asc, fiction",
"Python, price_desc, non_fiction",
"Java, price_desc, fiction",
"Python, price_asc, non_fiction"
})
void searchWithDifferentCombinations(String keyword, String sort, String category) {
List<Book> results = searchService.search(keyword, sort, category);
assertNotNull(results);
}
场景测试法
基于用户故事或业务流程设计测试场景。
示例:用户注册流程
@Test
void successfulUserRegistrationFlow() {
// Step 1: 提交注册信息
RegistrationResponse resp1 = client.register("test@example.com", "Password123!");
assertTrue(resp1.isSuccess());
// Step 2: 验证邮箱
VerificationResponse resp2 = client.verifyEmail(resp1.getToken());
assertTrue(resp2.isVerified());
// Step 3: 登录
LoginResponse resp3 = client.login("test@example.com", "Password123!");
assertNotNull(resp3.getToken());
}
Java单元测试最佳实践
标准测试结构(AAA Pattern)
- Arrange:准备测试数据
- Act:执行被测方法
- Assert:验证结果
@Test
void calculateDiscount_WhenPremiumUserAndOrderOver100_ShouldReturn20Percent() {
// Arrange
User user = new User(UserType.PREMIUM);
Order order = new Order(new BigDecimal("150.00"));
// Act
BigDecimal discount = discountService.calculateDiscount(user, order);
// Assert
assertEquals(new BigDecimal("0.20"), discount);
}
使用Mockito隔离依赖
@Test
void orderService_WhenProductNotAvailable_ShouldThrowException() {
// Mock仓库服务
ProductRepository repo = mock(ProductRepository.class);
when(repo.findBySku("BOOK-001")).thenReturn(Optional.empty());
// 注入mock
OrderService service = new OrderService(repo);
// 执行并验证
assertThrows(ProductNotFoundException.class,
() -> service.createOrder("BOOK-001", 1));
verify(repo).findBySku("BOOK-001");
}
参数化测试
@ParameterizedTest
@ValueSource(ints = {0, 1, 10, 100})
void calculateShipping_WhenQuantityValid_ShouldReturnPositive(int quantity) {
BigDecimal shipping = shippingService.calculate(quantity);
assertTrue(shipping.compareTo(BigDecimal.ZERO) > 0);
}
@CsvSource({
"2, 5.99",
"5, 8.99",
"10, 12.99"
})
void calculateShipping_WhenQuantityChange_ShouldReturnCorrectAmount(int quantity, String expected) {
BigDecimal shipping = shippingService.calculate(quantity);
assertEquals(new BigDecimal(expected), shipping);
}
测试异常
// 验证异常类型和消息
@Test
void createOrder_WhenInvalidEmail_ShouldThrowException() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> userService.createUser("invalid-email", "pass123")
);
assertTrue(ex.getMessage().contains("Invalid email format"));
}
常用测试工具与框架
| 工具 | 用途 | 示例 |
|---|---|---|
| JUnit 5 | 单元测试框架 | @Test, @ParameterizedTest |
| Mockito | Mock和Stub | mock(), when().thenReturn() |
| AssertJ | 流畅断言 | assertThat(result).isNotNull() |
| Testcontainers | 集成测试容器 | @Container PostgreSQLContainer |
| REST Assured | API测试 | given().get("/api/books").then() |
| Selenium | UI自动化 | WebDriver.findElement(By.id("submit")) |
测试设计检查清单
✅ 功能性测试
- [ ] 正常业务流程通过
- [ ] 边界值全覆盖
- [ ] 异常路径处理
- [ ] 数据完整性验证
✅ 非功能性测试
- [ ] 性能指标(响应时间<200ms)
- [ ] 并发安全性(多线程场景)
- [ ] 数据一致性(事务回滚)
- [ ] 缓存命中/失效
✅ 代码质量
- [ ] 测试覆盖率>80%
- [ ] 无重复测试逻辑
- [ ] 测试独立可执行
- [ ] 测试数据可清理
实战案例:用户订单系统
需求
- 用户创建订单时验证库存
- 黄金会员享受10%折扣
- 订单超过1000元免运费
测试用例设计
public class OrderServiceTest {
@Mock
private InventoryClient inventoryClient;
@Mock
private UserRepository userRepo;
@InjectMocks
private OrderService orderService;
// 1. 正常创建黄金会员订单
@Test
void createOrder_GoldMemberWithSufficientStock_ShouldApplyDiscount() {
User user = new User(1L, UserType.GOLD);
when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(inventoryClient.checkStock("BOOK-1", 2)).thenReturn(true);
Order order = orderService.createOrder(1L, "BOOK-1", 2);
assertEquals(new BigDecimal("180.00"), order.getTotal()); // 100*2*0.9
assertEquals(new BigDecimal("0.00"), order.getShipping()); // 免运费
}
// 2. 库存不足场景
@Test
void createOrder_WhenStockInsufficient_ShouldThrowException() {
when(inventoryClient.checkStock("BOOK-1", 10)).thenReturn(false);
assertThrows(InsufficientStockException.class,
() -> orderService.createOrder(1L, "BOOK-1", 10));
}
// 3. 普通会员订单,运费计算
@Test
void createOrder_NormalMemberUnder1000_ShouldChargeShipping() {
User user = new User(1L, UserType.NORMAL);
when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(inventoryClient.checkStock("BOOK-1", 1)).thenReturn(true);
Order order = orderService.createOrder(1L, "BOOK-1", 1);
assertEquals(new BigDecimal("100.00"), order.getTotal()); // 原价
assertEquals(new BigDecimal("10.00"), order.getShipping()); // 运费
}
// 4. 并发下单测试
@Test
void createOrder_ConcurrentAccess_ShouldHandleCorrectly() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
try {
orderService.createOrder(1L, "LIMITED-BOOK", 1);
} finally {
latch.countDown();
}
});
}
latch.await();
// 验证库存只扣减1次
verify(inventoryClient, times(1)).deductStock("LIMITED-BOOK", 1);
}
}
文档与维护
- 测试命名:
[MethodName]_[Scenario]_[ExpectedResult](如calculateDiscount_PremiumUser_Returns20Percent) - Gherkin风格(适用于集成测试):
Scenario: 黄金会员下单免运费 Given 用户是黄金会员 And 商品库存充足 When 用户下单金额超过1000元 Then 订单会免运费
- 注释规范:仅需在复杂逻辑处添加说明
- 定期review:每季度检查测试用例是否反映最新业务规则
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播