跳转至

Java 测试

此文件使用 Java 特定内容扩展 common/testing.md。

测试框架

  • JUnit 5(@Test、@ParameterizedTest、@Nested、@DisplayName)
  • AssertJ — 流畅断言(assertThat(result).isEqualTo(expected))
  • Mockito — 依赖项模拟
  • Testcontainers — 需要数据库或服务的集成测试

测试结构

src/test/java/com/example/app/
  service/           # サービス層のユニットテスト
  controller/        # Web 層 / API テスト
  repository/        # データアクセステスト
  integration/       # クロスレイヤー統合テスト

将 src/main/java 中的包结构镜像到 src/test/java。

单元测试模式

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    private OrderService orderService;

    @BeforeEach
    void setUp() {
        orderService = new OrderService(orderRepository);
    }

    @Test
    @DisplayName("findById returns order when exists")
    void findById_existingOrder_returnsOrder() {
        var order = new Order(1L, "Alice", BigDecimal.TEN);
        when(orderRepository.findById(1L)).thenReturn(Optional.of(order));

        var result = orderService.findById(1L);

        assertThat(result.customerName()).isEqualTo("Alice");
        verify(orderRepository).findById(1L);
    }

    @Test
    @DisplayName("findById throws when order not found")
    void findById_missingOrder_throws() {
        when(orderRepository.findById(99L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> orderService.findById(99L))
            .isInstanceOf(OrderNotFoundException.class)
            .hasMessageContaining("99");
    }
}

参数化测试

@ParameterizedTest
@CsvSource({
    "100.00, 10, 90.00",
    "50.00, 0, 50.00",
    "200.00, 25, 150.00"
})
@DisplayName("discount applied correctly")
void applyDiscount(BigDecimal price, int pct, BigDecimal expected) {
    assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected);
}

集成测试

使用 Testcontainers 进行真实数据库集成:

@Testcontainers
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    private OrderRepository repository;

    @BeforeEach
    void setUp() {
        var dataSource = new PGSimpleDataSource();
        dataSource.setUrl(postgres.getJdbcUrl());
        dataSource.setUser(postgres.getUsername());
        dataSource.setPassword(postgres.getPassword());
        repository = new JdbcOrderRepository(dataSource);
    }

    @Test
    void save_and_findById() {
        var saved = repository.save(new Order(null, "Bob", BigDecimal.ONE));
        var found = repository.findById(saved.getId());
        assertThat(found).isPresent();
    }
}

有关 Spring Boot 集成测试,请参见技能:springboot-tdd。 有关 Quarkus 集成测试,请参见技能:quarkus-tdd。

测试命名

使用 @DisplayName 编写描述性名称: - 方法名使用 methodName_scenario_expectedBehavior() - 报告中使用 @DisplayName("人間が読める説明")

覆盖率

  • 目标行覆盖率 80% 以上
  • 覆盖率报告使用 JaCoCo
  • 专注于服务和领域逻辑 — 跳过显而易见的 getter/配置类

参考

请参见技能:springboot-tdd,了解使用 MockMvc 和 Testcontainers 的 Spring Boot TDD 模式。 请参见技能:quarkus-tdd,了解使用 REST Assured 和 Dev Services 的 Quarkus TDD 模式。 请参见技能:java-coding-standards,了解测试要求。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC