mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
134 lines
3.4 KiB
Markdown
134 lines
3.4 KiB
Markdown
---
|
|
paths:
|
|
- "**/*.java"
|
|
---
|
|
|
|
# Java 测试
|
|
|
|
> 本文档扩展了 [common/testing.md](../common/testing.md) 中与 Java 相关的内容。
|
|
|
|
## 测试框架
|
|
|
|
* **JUnit 5** (`@Test`, `@ParameterizedTest`, `@Nested`, `@DisplayName`)
|
|
* **AssertJ** 用于流式断言 (`assertThat(result).isEqualTo(expected)`)
|
|
* **Mockito** 用于模拟依赖
|
|
* **Testcontainers** 用于需要数据库或服务的集成测试
|
|
|
|
## 测试组织
|
|
|
|
```
|
|
src/test/java/com/example/app/
|
|
service/ # Unit tests for service layer
|
|
controller/ # Web layer / API tests
|
|
repository/ # Data access tests
|
|
integration/ # Cross-layer integration tests
|
|
```
|
|
|
|
在 `src/test/java` 中镜像 `src/main/java` 的包结构。
|
|
|
|
## 单元测试模式
|
|
|
|
```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");
|
|
}
|
|
}
|
|
```
|
|
|
|
## 参数化测试
|
|
|
|
```java
|
|
@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 进行真实的数据库集成:
|
|
|
|
```java
|
|
@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`。
|
|
|
|
## 测试命名
|
|
|
|
使用带有 `@DisplayName` 的描述性名称:
|
|
|
|
* `methodName_scenario_expectedBehavior()` 用于方法名
|
|
* `@DisplayName("human-readable description")` 用于报告
|
|
|
|
## 覆盖率
|
|
|
|
* 目标为 80%+ 的行覆盖率
|
|
* 使用 JaCoCo 生成覆盖率报告
|
|
* 重点关注服务和领域逻辑 — 跳过简单的 getter/配置类
|
|
|
|
## 参考
|
|
|
|
关于使用 MockMvc 和 Testcontainers 的 Spring Boot TDD 模式,请参阅技能:`springboot-tdd`。
|
|
关于测试期望,请参阅技能:`java-coding-standards`。
|