Java测试用例案例如何设计

wen java案例 23

本文目录导读:

Java测试用例案例如何设计

  1. 测试金字塔与分层设计
  2. 测试用例设计方法
  3. Java单元测试最佳实践
  4. 常用测试工具与框架
  5. 测试设计检查清单
  6. 实战案例:用户订单系统
  7. 文档与维护

测试金字塔与分层设计

单元测试(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%
  • [ ] 无重复测试逻辑
  • [ ] 测试独立可执行
  • [ ] 测试数据可清理

实战案例:用户订单系统

需求

  1. 用户创建订单时验证库存
  2. 黄金会员享受10%折扣
  3. 订单超过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:每季度检查测试用例是否反映最新业务规则

抱歉,评论功能暂时关闭!