260 lines
6.7 KiB
Markdown
260 lines
6.7 KiB
Markdown
# 测试工具包使用指南
|
||
|
||
junboV2 项目集成了测试工具包,提供 YAML 测试数据加载、通用断言等功能。
|
||
|
||
## 📦 功能组件
|
||
|
||
### 1. TestDataLoader - YAML 测试数据加载器
|
||
|
||
从 YAML 文件加载测试数据,支持类型安全和泛型。
|
||
|
||
### 2. BaseTestCase - 测试用例基类
|
||
|
||
提供测试用例的通用字段(名称、描述、跳过标记等)。
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 步骤 1:创建测试数据类
|
||
|
||
```java
|
||
@Data
|
||
@EqualsAndHashCode(callSuper = true)
|
||
public class CreateUserTestCase extends BaseTestCase {
|
||
private Input input;
|
||
private Expected expected;
|
||
|
||
@Data
|
||
public static class Input {
|
||
private String username;
|
||
private String email;
|
||
private Integer age;
|
||
}
|
||
|
||
@Data
|
||
public static class Expected {
|
||
private boolean success;
|
||
private String errorCode;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤 2:创建 YAML 测试数据
|
||
|
||
文件位置:`src/test/resources/testdata/user-service/create-user-cases.yaml`
|
||
|
||
```yaml
|
||
testCases:
|
||
- name: "正常创建用户"
|
||
description: "使用有效数据创建用户"
|
||
input:
|
||
username: "张三"
|
||
email: "zhangsan@example.com"
|
||
age: 25
|
||
expected:
|
||
success: true
|
||
|
||
- name: "用户名为空"
|
||
description: "用户名为空应该失败"
|
||
input:
|
||
username: ""
|
||
email: "test@example.com"
|
||
age: 25
|
||
expected:
|
||
success: false
|
||
errorCode: "INVALID_USERNAME"
|
||
|
||
- name: "邮箱格式错误"
|
||
description: "邮箱格式不正确应该失败"
|
||
input:
|
||
username: "李四"
|
||
email: "invalid-email"
|
||
age: 25
|
||
expected:
|
||
success: false
|
||
errorCode: "INVALID_EMAIL"
|
||
|
||
- name: "年龄为负数"
|
||
description: "年龄为负数应该被拒绝"
|
||
skip: true
|
||
skipReason: "暂未实现年龄验证"
|
||
input:
|
||
username: "王五"
|
||
email: "wangwu@example.com"
|
||
age: -1
|
||
expected:
|
||
success: false
|
||
errorCode: "INVALID_AGE"
|
||
```
|
||
|
||
### 步骤 3:编写参数化测试
|
||
|
||
```java
|
||
@SpringBootTest
|
||
@DisplayName("用户服务测试")
|
||
class UserServiceTest {
|
||
|
||
@Autowired
|
||
private UserService userService;
|
||
|
||
@ParameterizedTest(name = "{0}")
|
||
@MethodSource("loadCreateUserCases")
|
||
@DisplayName("创建用户")
|
||
void shouldCreateUser(CreateUserTestCase testCase) {
|
||
// 跳过标记的测试用例
|
||
if (testCase.isSkip()) {
|
||
System.out.println("跳过: " + testCase.getSkipReason());
|
||
return;
|
||
}
|
||
|
||
// Given
|
||
CreateUserRequest request = new CreateUserRequest();
|
||
request.setUsername(testCase.getInput().getUsername());
|
||
request.setEmail(testCase.getInput().getEmail());
|
||
request.setAge(testCase.getInput().getAge());
|
||
|
||
// When & Then
|
||
if (testCase.getExpected().isSuccess()) {
|
||
User user = userService.createUser(request);
|
||
assertThat(user).isNotNull();
|
||
assertThat(user.getUsername()).isEqualTo(request.getUsername());
|
||
} else {
|
||
assertThatThrownBy(() -> userService.createUser(request))
|
||
.isInstanceOf(BusinessException.class)
|
||
.hasMessageContaining(testCase.getExpected().getErrorCode());
|
||
}
|
||
}
|
||
|
||
static Stream<CreateUserTestCase> loadCreateUserCases() {
|
||
return TestDataLoader.load(
|
||
"user-service/create-user-cases.yaml",
|
||
CreateUserTestCase.class
|
||
).stream();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📁 目录结构
|
||
|
||
```
|
||
src/test/
|
||
├── java/info/panli/junbo/
|
||
│ ├── test/support/ # 测试工具包
|
||
│ │ ├── TestDataLoader.java # YAML 数据加载器
|
||
│ │ └── BaseTestCase.java # 测试用例基类
|
||
│ │
|
||
│ ├── organization/ # 组织领域测试
|
||
│ │ └── repository/
|
||
│ │ └── EmployeeRepositoryExampleTest.java
|
||
│ │
|
||
│ ├── attendance/ # 考勤领域测试
|
||
│ ├── scrum/ # 迭代领域测试
|
||
│ ├── kpa/ # KPA 领域测试
|
||
│ └── incentive/ # 激励领域测试
|
||
│
|
||
└── resources/
|
||
└── testdata/ # 测试数据目录
|
||
├── organization/
|
||
│ └── find-by-name-cases.yaml
|
||
├── attendance/
|
||
├── scrum/
|
||
├── kpa/
|
||
└── incentive/
|
||
```
|
||
|
||
## 💡 最佳实践
|
||
|
||
### 1. YAML 文件命名
|
||
|
||
- 使用小写字母和连字符
|
||
- 格式:`{功能}-cases.yaml`
|
||
- 示例:`create-user-cases.yaml`, `find-by-id-cases.yaml`
|
||
|
||
### 2. 测试用例命名
|
||
|
||
- 使用 `@DisplayName` 提供中文描述
|
||
- `@ParameterizedTest` 的 name 使用 `"{0}"` 显示测试用例名称
|
||
|
||
### 3. 测试数据组织
|
||
|
||
- 按领域划分目录:`testdata/organization/`, `testdata/attendance/`
|
||
- 每个服务/Repository 一个子目录
|
||
- 相关测试用例放在同一个 YAML 文件
|
||
|
||
### 4. 跳过测试
|
||
|
||
```yaml
|
||
testCases:
|
||
- name: "未实现的功能"
|
||
skip: true
|
||
skipReason: "等待后端 API 完成"
|
||
input: {...}
|
||
expected: {...}
|
||
```
|
||
|
||
在测试中检查:
|
||
|
||
```java
|
||
@ParameterizedTest(name = "{0}")
|
||
@MethodSource("loadTestCases")
|
||
void testSomething(MyTestCase testCase) {
|
||
if (testCase.isSkip()) {
|
||
System.out.println("跳过: " + testCase.getSkipReason());
|
||
return;
|
||
}
|
||
// 测试逻辑
|
||
}
|
||
```
|
||
|
||
## 🔧 配置
|
||
|
||
依赖已在 `unittest.gradle` 中配置:
|
||
|
||
```groovy
|
||
dependencies {
|
||
// JUnit 5 + Mockito + AssertJ
|
||
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0'
|
||
testImplementation 'org.mockito:mockito-core:4.11.0'
|
||
testImplementation 'org.assertj:assertj-core:3.19.0'
|
||
|
||
// YAML 支持
|
||
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2'
|
||
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2'
|
||
|
||
// Spring Boot Test
|
||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||
}
|
||
```
|
||
|
||
## 📚 示例代码
|
||
|
||
查看以下示例了解如何使用:
|
||
|
||
1. **Repository 测试**:`EmployeeRepositoryExampleTest.java`
|
||
2. **YAML 数据**:`testdata/organization/find-by-name-cases.yaml`
|
||
|
||
## 🎯 运行测试
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
./gradlew test
|
||
|
||
# 运行特定测试类
|
||
./gradlew test --tests "EmployeeRepositoryExampleTest"
|
||
|
||
# 查看测试报告
|
||
# 报告位置:build/reports/tests/test/index.html
|
||
```
|
||
|
||
## 📖 参考资料
|
||
|
||
- JUnit 5: https://junit.org/junit5/docs/current/user-guide/
|
||
- AssertJ: https://assertj.github.io/doc/
|
||
- Mockito: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
|
||
- Spring Boot Test: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing
|
||
|
||
---
|
||
|
||
**文档版本**: 1.0
|
||
**创建日期**: 2026-02-06
|
||
**维护者**: junboV2 团队
|