@xulthekl/team-flow 0.32.2 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +56 -0
- package/CONTRIBUTING.md +44 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/agents/architecture-design.md +1 -34
- package/agents/architecture-reviewer.md +1 -42
- package/agents/bug-investigator.md +1 -37
- package/agents/build-executor.md +1 -22
- package/agents/change-split-auditor.md +1 -42
- package/agents/code-reviewer.md +1 -42
- package/agents/contract-builder.md +1 -22
- package/agents/cross-change-consistency-checker.md +2 -43
- package/agents/need-explorer.md +1 -22
- package/agents/prd-completeness-reviewer.md +1 -47
- package/agents/prototype-builder.md +1 -41
- package/agents/prototype-env-scout.md +1 -26
- package/agents/prototype-reviewer.md +1 -41
- package/agents/release-archivist.md +1 -22
- package/agents/spec-writer.md +1 -22
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +1 -0
- package/docs/solutions/cross-phase/2026-08-04-no-summary.md +17 -0
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/plugin.json +1 -1
- package/scripts/lib/conventions-generator.mjs +350 -0
- package/scripts/lib/test-record.mjs +65 -2
- package/skills/e2e/SKILL.md +1 -1
- package/skills/test-strategy/SKILL.md +38 -1
- package/skills/test-strategy/references/integration-test-contracts.md +237 -0
- package/skills/test-strategy/references/integration-test-isolation.md +346 -0
- package/skills/test-strategy/references/test-quality-rules.md +292 -0
- package/skills/workflow-bootstrap/SKILL.md +40 -3
- package/templates/agent-template.md +41 -0
- package/templates/conventions/_manifest.json +39 -0
- package/templates/conventions/glaf4-compliant/java-testing.md +367 -0
- package/templates/conventions/glaf4-compliant/spring-patterns.md +415 -0
- package/templates/conventions/js-testing.md +261 -0
- package/templates/conventions/python-testing.md +333 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Integration Test Contracts(集成测试契约)
|
|
2
|
+
|
|
3
|
+
> 来源:glaf4-test social-test-contracts.md 的通用模式(v0.13 §58)
|
|
4
|
+
> 用途:集成测试的组织方法论,contract-builder 生成矩阵时参考
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、核心概念
|
|
9
|
+
|
|
10
|
+
**社交测试(Social Test)**:跨类/跨层交互的测试,需要声明式契约来明确测试边界和依赖。
|
|
11
|
+
|
|
12
|
+
**契约的作用**:
|
|
13
|
+
- 明确测试的输入和输出
|
|
14
|
+
- 声明依赖的真实/mock/stub
|
|
15
|
+
- 定义测试隔离和清理策略
|
|
16
|
+
- 确保测试可重复和可维护
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 二、五种契约类型
|
|
21
|
+
|
|
22
|
+
### 1. 入口契约(Entry Contract)
|
|
23
|
+
|
|
24
|
+
**定义**:系统边界入口声明
|
|
25
|
+
|
|
26
|
+
**内容**:
|
|
27
|
+
- API 端点(URL、HTTP 方法、请求格式)
|
|
28
|
+
- 消息队列(exchange、routingKey、消息格式)
|
|
29
|
+
- 定时任务(cron 表达式、触发条件)
|
|
30
|
+
|
|
31
|
+
**示例**:
|
|
32
|
+
```markdown
|
|
33
|
+
## 入口契约
|
|
34
|
+
|
|
35
|
+
- API: `POST /api/users`
|
|
36
|
+
- Content-Type: application/json
|
|
37
|
+
- 请求体: `{ "name": "string", "email": "string" }`
|
|
38
|
+
- 认证: Bearer Token
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. 协作者契约(Collaborator Contract)
|
|
42
|
+
|
|
43
|
+
**定义**:依赖分级声明
|
|
44
|
+
|
|
45
|
+
**分级**:
|
|
46
|
+
| 级别 | 说明 | 使用场景 |
|
|
47
|
+
|------|------|---------|
|
|
48
|
+
| **真实(Real)** | 使用真实实现 | Repository 层、内部 Service |
|
|
49
|
+
| **Mock** | 使用 mock 框架创建 | 外部 API、第三方服务 |
|
|
50
|
+
| **Stub** | 使用固定返回值 | 简单依赖、配置服务 |
|
|
51
|
+
|
|
52
|
+
**示例**:
|
|
53
|
+
```markdown
|
|
54
|
+
## 协作者契约
|
|
55
|
+
|
|
56
|
+
| 协作者 | 级别 | 理由 |
|
|
57
|
+
|--------|------|------|
|
|
58
|
+
| UserRepository | Real | 测试真实持久化逻辑 |
|
|
59
|
+
| EmailService | Mock | 外部服务,避免真实发送 |
|
|
60
|
+
| ConfigService | Stub | 返回固定配置值 |
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. 数据契约(Data Contract)
|
|
64
|
+
|
|
65
|
+
**定义**:测试数据规格声明
|
|
66
|
+
|
|
67
|
+
**内容**:
|
|
68
|
+
- 输入数据格式和约束
|
|
69
|
+
- 输出数据格式和验证点
|
|
70
|
+
- 测试数据准备方式
|
|
71
|
+
|
|
72
|
+
**示例**:
|
|
73
|
+
```markdown
|
|
74
|
+
## 数据契约
|
|
75
|
+
|
|
76
|
+
### 输入
|
|
77
|
+
- name: string, 1-50 字符
|
|
78
|
+
- email: string, 有效邮箱格式
|
|
79
|
+
|
|
80
|
+
### 输出
|
|
81
|
+
- User 对象,包含 id、name、email、createdAt
|
|
82
|
+
- id: 雪花算法生成,非空
|
|
83
|
+
- createdAt: 当前时间,非空
|
|
84
|
+
|
|
85
|
+
### 准备方式
|
|
86
|
+
- 使用 Builder 模式构建测试数据
|
|
87
|
+
- 每个测试独立数据,避免共享状态
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 4. 中间件契约(Middleware Contract)
|
|
91
|
+
|
|
92
|
+
**定义**:基础设施交互规格
|
|
93
|
+
|
|
94
|
+
**内容**:
|
|
95
|
+
| 中间件 | 交互方式 | 测试策略 |
|
|
96
|
+
|--------|---------|---------|
|
|
97
|
+
| **数据库** | SQL 查询/写入 | H2 内存库 / @Transactional 回滚 |
|
|
98
|
+
| **消息队列** | 发送/接收消息 | rabbitmq-mock / embedded broker |
|
|
99
|
+
| **缓存** | 读写 Redis | embedded-redis / mock |
|
|
100
|
+
| **外部 HTTP** | 调用第三方 API | MockWebServer / WireMock |
|
|
101
|
+
|
|
102
|
+
**示例**:
|
|
103
|
+
```markdown
|
|
104
|
+
## 中间件契约
|
|
105
|
+
|
|
106
|
+
| 中间件 | 交互 | 测试策略 |
|
|
107
|
+
|--------|------|---------|
|
|
108
|
+
| MySQL | INSERT/SELECT | H2 内存库,@Transactional 回滚 |
|
|
109
|
+
| RabbitMQ | 发送用户创建事件 | rabbitmq-mock |
|
|
110
|
+
| Redis | 缓存用户信息 | embedded-redis (port 6378) |
|
|
111
|
+
| 支付网关 | 调用支付 API | MockWebServer |
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 5. 清理契约(Cleanup Contract)
|
|
115
|
+
|
|
116
|
+
**定义**:测试隔离清理声明
|
|
117
|
+
|
|
118
|
+
**原则**:
|
|
119
|
+
- 每个测试独立,不共享状态
|
|
120
|
+
- 测试后清理所有创建的数据
|
|
121
|
+
- 异步操作需要等待完成
|
|
122
|
+
|
|
123
|
+
**策略**:
|
|
124
|
+
| 场景 | 清理方式 |
|
|
125
|
+
|------|---------|
|
|
126
|
+
| **同步操作** | @Transactional 自动回滚 |
|
|
127
|
+
| **异步操作** | 手动清理(@AfterEach) |
|
|
128
|
+
| **消息队列** | 清空队列 |
|
|
129
|
+
| **缓存** | 清空 key |
|
|
130
|
+
|
|
131
|
+
**示例**:
|
|
132
|
+
```markdown
|
|
133
|
+
## 清理契约
|
|
134
|
+
|
|
135
|
+
- 数据库:@Transactional 自动回滚
|
|
136
|
+
- 消息队列:@AfterEach 清空队列
|
|
137
|
+
- 缓存:@AfterEach 清空测试 key
|
|
138
|
+
- ThreadLocal:@AfterEach 清理 TraceIdHolder
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 三、契约声明格式
|
|
144
|
+
|
|
145
|
+
在 test-matrix.md 的集成测试 case 中,使用以下格式声明契约:
|
|
146
|
+
|
|
147
|
+
```markdown
|
|
148
|
+
| ID | work_mode | test_tier | design_method | description | priority | mock |
|
|
149
|
+
|----|-----------|-----------|---------------|-------------|----------|------|
|
|
150
|
+
| TC-010 | TDD | integration | equivalence | [contract] 创建用户:POST /api/users | P0 | entry:POST /api/users, collaborator:EmailService=Mock, data:User{name,email}, cleanup:@Transactional |
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**字段说明**:
|
|
154
|
+
- `entry`: 入口契约
|
|
155
|
+
- `collaborator`: 协作者契约(Real/Mock/Stub)
|
|
156
|
+
- `data`: 数据契约
|
|
157
|
+
- `middleware`: 中间件契约
|
|
158
|
+
- `cleanup`: 清理契约
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 四、矩阵生成指南
|
|
163
|
+
|
|
164
|
+
### contract-builder 生成集成测试矩阵时:
|
|
165
|
+
|
|
166
|
+
1. **识别入口**:从 specs/*.md 中提取 API 端点、消息队列、定时任务
|
|
167
|
+
2. **分析依赖**:从代码中识别所有依赖,按真实/mock/stub 分级
|
|
168
|
+
3. **定义数据**:明确输入输出数据格式
|
|
169
|
+
4. **选择中间件**:识别涉及的基础设施,选择测试策略
|
|
170
|
+
5. **设计清理**:根据操作类型(同步/异步)设计清理策略
|
|
171
|
+
|
|
172
|
+
### 示例:用户注册功能
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
## 集成测试矩阵
|
|
176
|
+
|
|
177
|
+
### TC-010: 创建用户成功
|
|
178
|
+
- 入口:POST /api/users
|
|
179
|
+
- 协作者:UserRepository=Real, EmailService=Mock
|
|
180
|
+
- 数据:{name:"John", email:"john@example.com"}
|
|
181
|
+
- 中间件:MySQL=H2
|
|
182
|
+
- 清理:@Transactional
|
|
183
|
+
|
|
184
|
+
### TC-011: 创建用户失败(邮箱已存在)
|
|
185
|
+
- 入口:POST /api/users
|
|
186
|
+
- 协作者:UserRepository=Real(预先插入重复数据)
|
|
187
|
+
- 数据:{name:"John", email:"existing@example.com"}
|
|
188
|
+
- 中间件:MySQL=H2
|
|
189
|
+
- 清理:@Transactional
|
|
190
|
+
- 预期:409 Conflict
|
|
191
|
+
|
|
192
|
+
### TC-012: 创建用户后发送欢迎邮件
|
|
193
|
+
- 入口:POST /api/users
|
|
194
|
+
- 协作者:UserRepository=Real, EmailService=Mock
|
|
195
|
+
- 数据:{name:"John", email:"john@example.com"}
|
|
196
|
+
- 中间件:MySQL=H2, RabbitMQ=rabbitmq-mock
|
|
197
|
+
- 清理:@Transactional + 清空队列
|
|
198
|
+
- 验证:EmailService.sendWelcomeEmail() 被调用
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 五、最佳实践
|
|
204
|
+
|
|
205
|
+
### 1. 契约最小化
|
|
206
|
+
- 只声明必要的契约
|
|
207
|
+
- 避免过度 mock(内部依赖尽量用真实实现)
|
|
208
|
+
|
|
209
|
+
### 2. 契约可读性
|
|
210
|
+
- 使用表格格式,清晰易读
|
|
211
|
+
- 提供理由说明(为什么选择 Mock/Stub)
|
|
212
|
+
|
|
213
|
+
### 3. 契约可维护性
|
|
214
|
+
- 契约与测试代码同步更新
|
|
215
|
+
- 定期审查契约的有效性
|
|
216
|
+
|
|
217
|
+
### 4. 契约复用
|
|
218
|
+
- 相似功能的契约可以复用
|
|
219
|
+
- 提取公共契约模板
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 六、与 test-strategy 的关系
|
|
224
|
+
|
|
225
|
+
集成测试契约是 test-strategy §1(Design Method 选择规则)的补充:
|
|
226
|
+
- **基础级**(equivalence/boundary/error):适用于所有测试
|
|
227
|
+
- **扩展级**(path/state/exception/reject):条件触发
|
|
228
|
+
- **高级**(permission/idempotency/concurrency/contract):场景触发
|
|
229
|
+
- **contract**:跨模块/跨服务接口契约,需要声明集成测试契约
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 变更记录
|
|
234
|
+
|
|
235
|
+
| 日期 | 版本 | 变更内容 |
|
|
236
|
+
|------|------|---------|
|
|
237
|
+
| 2026-08-04 | v1.0 | 从 glaf4-test social-test-contracts.md 抽取通用模式 |
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Integration Test Isolation(集成测试隔离分级)
|
|
2
|
+
|
|
3
|
+
> 来源:glaf4-test h2-rabbitmq-redis.md 的隔离策略(v0.13 §59)
|
|
4
|
+
> 用途:集成测试的分层策略,write-integration-worker 参考
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、核心原则
|
|
9
|
+
|
|
10
|
+
**测试隔离**:每个测试独立运行,不依赖其他测试的状态,不污染共享资源。
|
|
11
|
+
|
|
12
|
+
**隔离级别**:
|
|
13
|
+
- **L1:进程内隔离**(最严格):H2、embedded-redis、rabbitmq-mock
|
|
14
|
+
- **L2:事务隔离**:@Transactional 自动回滚
|
|
15
|
+
- **L3:手动清理**:@AfterEach 手动清理资源
|
|
16
|
+
- **L4:无隔离**(最宽松):共享真实基础设施(仅限开发环境)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 二、基础设施隔离策略
|
|
21
|
+
|
|
22
|
+
### 1. 数据库(MySQL/PostgreSQL)
|
|
23
|
+
|
|
24
|
+
| 策略 | 适用场景 | 实现方式 | 优缺点 |
|
|
25
|
+
|------|---------|---------|--------|
|
|
26
|
+
| **H2 内存库** | Repository 层测试 | `@DataJpaTest` + H2 依赖 | ✅ 快速、隔离 ❌ SQL 兼容性差异 |
|
|
27
|
+
| **@Transactional** | Service 层测试 | 测试类加 `@Transactional` | ✅ 自动回滚 ❌ 异步操作不适用 |
|
|
28
|
+
| **Testcontainers** | 需要真实数据库 | Docker 容器 | ✅ 真实环境 ❌ 慢、需要 Docker |
|
|
29
|
+
|
|
30
|
+
**推荐**:
|
|
31
|
+
- Repository 层:H2 内存库
|
|
32
|
+
- Service 层:@Transactional(同步操作)
|
|
33
|
+
- API 层:@Transactional 或 Testcontainers
|
|
34
|
+
|
|
35
|
+
**H2 配置示例**:
|
|
36
|
+
```yaml
|
|
37
|
+
# application-test.yml
|
|
38
|
+
spring:
|
|
39
|
+
datasource:
|
|
40
|
+
url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_UPPER=false
|
|
41
|
+
driver-class-name: org.h2.Driver
|
|
42
|
+
jpa:
|
|
43
|
+
hibernate:
|
|
44
|
+
ddl-auto: create-drop
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. 消息队列(RabbitMQ/Kafka)
|
|
48
|
+
|
|
49
|
+
| 策略 | 适用场景 | 实现方式 | 优缺点 |
|
|
50
|
+
|------|---------|---------|--------|
|
|
51
|
+
| **rabbitmq-mock** | MQ 生产者测试 | `MockConnectionFactory` | ✅ 快速、隔离 ❌ 不测试真实 MQ |
|
|
52
|
+
| **embedded broker** | MQ 消费者测试 | embedded-rabbitmq | ✅ 真实协议 ❌ 慢、资源占用 |
|
|
53
|
+
| **Testcontainers** | 端到端测试 | Docker 容器 | ✅ 真实环境 ❌ 慢、需要 Docker |
|
|
54
|
+
|
|
55
|
+
**推荐**:
|
|
56
|
+
- 生产者:rabbitmq-mock
|
|
57
|
+
- 消费者:embedded broker 或 Testcontainers
|
|
58
|
+
|
|
59
|
+
**rabbitmq-mock 配置示例**:
|
|
60
|
+
```java
|
|
61
|
+
@Configuration
|
|
62
|
+
public class RabbitMockConfig {
|
|
63
|
+
@Bean
|
|
64
|
+
public ConnectionFactory connectionFactory() {
|
|
65
|
+
return new MockConnectionFactory();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. 缓存(Redis)
|
|
71
|
+
|
|
72
|
+
| 策略 | 适用场景 | 实现方式 | 优缺点 |
|
|
73
|
+
|------|---------|---------|--------|
|
|
74
|
+
| **embedded-redis** | 缓存测试 | embedded-redis(port 6378) | ✅ 真实协议 ❌ ARM64 兼容问题 |
|
|
75
|
+
| **mock** | 简单缓存逻辑 | Mockito mock | ✅ 快速 ❌ 不测试真实 Redis |
|
|
76
|
+
| **Testcontainers** | 端到端测试 | Docker 容器 | ✅ 真实环境 ❌ 慢、需要 Docker |
|
|
77
|
+
|
|
78
|
+
**推荐**:
|
|
79
|
+
- 简单缓存逻辑:mock
|
|
80
|
+
- 复杂缓存逻辑:embedded-redis
|
|
81
|
+
|
|
82
|
+
**embedded-redis 配置示例**:
|
|
83
|
+
```java
|
|
84
|
+
@Configuration
|
|
85
|
+
public class RedisTestConfig {
|
|
86
|
+
@Bean
|
|
87
|
+
public RedisServer redisServer() throws IOException {
|
|
88
|
+
RedisServer server = new RedisServer(6378);
|
|
89
|
+
server.start();
|
|
90
|
+
return server;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. 外部 HTTP(第三方 API)
|
|
96
|
+
|
|
97
|
+
| 策略 | 适用场景 | 实现方式 | 优缺点 |
|
|
98
|
+
|------|---------|---------|--------|
|
|
99
|
+
| **MockWebServer** | HTTP 客户端测试 | OkHttp MockWebServer | ✅ 真实 HTTP 协议 ❌ 只测试客户端 |
|
|
100
|
+
| **WireMock** | 复杂 API 模拟 | WireMock | ✅ 强大的模拟能力 ❌ 学习成本 |
|
|
101
|
+
| **@MockBean** | Spring Cloud Feign | Mockito mock | ✅ 简单 ❌ 不测试真实 HTTP |
|
|
102
|
+
|
|
103
|
+
**推荐**:
|
|
104
|
+
- 简单 HTTP 客户端:MockWebServer
|
|
105
|
+
- 复杂 API 模拟:WireMock
|
|
106
|
+
- Feign 客户端:@MockBean
|
|
107
|
+
|
|
108
|
+
**MockWebServer 配置示例**:
|
|
109
|
+
```java
|
|
110
|
+
@Test
|
|
111
|
+
void testCallExternalApi() throws Exception {
|
|
112
|
+
MockWebServer server = new MockWebServer();
|
|
113
|
+
server.enqueue(new MockResponse()
|
|
114
|
+
.setBody("{\"status\":\"ok\"}")
|
|
115
|
+
.setHeader("Content-Type", "application/json"));
|
|
116
|
+
|
|
117
|
+
// 调用被测方法
|
|
118
|
+
String result = externalService.callApi();
|
|
119
|
+
|
|
120
|
+
// 验证请求
|
|
121
|
+
RecordedRequest request = server.takeRequest();
|
|
122
|
+
assertEquals("/api/data", request.getPath());
|
|
123
|
+
assertEquals("ok", result);
|
|
124
|
+
|
|
125
|
+
server.shutdown();
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 三、分层隔离策略
|
|
132
|
+
|
|
133
|
+
### Repository 层
|
|
134
|
+
|
|
135
|
+
**策略**:H2 内存库 + @Transactional
|
|
136
|
+
|
|
137
|
+
**理由**:
|
|
138
|
+
- Repository 层只测试 SQL 和映射逻辑
|
|
139
|
+
- H2 内存库提供快速、隔离的测试环境
|
|
140
|
+
- @Transactional 确保测试后数据回滚
|
|
141
|
+
|
|
142
|
+
**示例**:
|
|
143
|
+
```java
|
|
144
|
+
@DataJpaTest
|
|
145
|
+
@Transactional
|
|
146
|
+
class UserRepositoryTest {
|
|
147
|
+
@Autowired
|
|
148
|
+
private UserRepository userRepository;
|
|
149
|
+
|
|
150
|
+
@Test
|
|
151
|
+
void testFindByEmail() {
|
|
152
|
+
// Arrange
|
|
153
|
+
User user = new User("John", "john@example.com");
|
|
154
|
+
userRepository.save(user);
|
|
155
|
+
|
|
156
|
+
// Act
|
|
157
|
+
Optional<User> found = userRepository.findByEmail("john@example.com");
|
|
158
|
+
|
|
159
|
+
// Assert
|
|
160
|
+
assertTrue(found.isPresent());
|
|
161
|
+
assertEquals("John", found.get().getName());
|
|
162
|
+
}
|
|
163
|
+
// 测试结束后自动回滚
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Service 层
|
|
168
|
+
|
|
169
|
+
**策略**:@Transactional(同步操作)或手动清理(异步操作)
|
|
170
|
+
|
|
171
|
+
**理由**:
|
|
172
|
+
- Service 层测试业务逻辑,需要真实依赖
|
|
173
|
+
- 同步操作用 @Transactional 自动回滚
|
|
174
|
+
- 异步操作需要手动清理(消息队列、缓存)
|
|
175
|
+
|
|
176
|
+
**示例(同步)**:
|
|
177
|
+
```java
|
|
178
|
+
@SpringBootTest
|
|
179
|
+
@Transactional
|
|
180
|
+
class UserServiceTest {
|
|
181
|
+
@Autowired
|
|
182
|
+
private UserService userService;
|
|
183
|
+
|
|
184
|
+
@Autowired
|
|
185
|
+
private UserRepository userRepository;
|
|
186
|
+
|
|
187
|
+
@Test
|
|
188
|
+
void testCreateUser() {
|
|
189
|
+
// Arrange
|
|
190
|
+
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
|
|
191
|
+
|
|
192
|
+
// Act
|
|
193
|
+
User user = userService.createUser(request);
|
|
194
|
+
|
|
195
|
+
// Assert
|
|
196
|
+
assertNotNull(user.getId());
|
|
197
|
+
assertEquals("John", user.getName());
|
|
198
|
+
}
|
|
199
|
+
// 测试结束后自动回滚
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**示例(异步)**:
|
|
204
|
+
```java
|
|
205
|
+
@SpringBootTest
|
|
206
|
+
class UserServiceAsyncTest {
|
|
207
|
+
@Autowired
|
|
208
|
+
private UserService userService;
|
|
209
|
+
|
|
210
|
+
@Autowired
|
|
211
|
+
private RabbitTemplate rabbitTemplate;
|
|
212
|
+
|
|
213
|
+
@AfterEach
|
|
214
|
+
void cleanup() {
|
|
215
|
+
// 手动清空消息队列
|
|
216
|
+
rabbitTemplate.execute(channel -> {
|
|
217
|
+
channel.queuePurge("user-events");
|
|
218
|
+
return null;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Test
|
|
223
|
+
void testCreateUserSendsEvent() {
|
|
224
|
+
// Arrange
|
|
225
|
+
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
|
|
226
|
+
|
|
227
|
+
// Act
|
|
228
|
+
User user = userService.createUser(request);
|
|
229
|
+
|
|
230
|
+
// Assert
|
|
231
|
+
assertNotNull(user.getId());
|
|
232
|
+
|
|
233
|
+
// 验证消息发送
|
|
234
|
+
Message message = rabbitTemplate.receive("user-events", 1000);
|
|
235
|
+
assertNotNull(message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### API 层
|
|
241
|
+
|
|
242
|
+
**策略**:MockMvc + @Transactional 或 Testcontainers
|
|
243
|
+
|
|
244
|
+
**理由**:
|
|
245
|
+
- API 层测试 HTTP 请求和响应
|
|
246
|
+
- 使用 MockMvc 模拟 HTTP 请求
|
|
247
|
+
- 数据库用 @Transactional 或 Testcontainers
|
|
248
|
+
|
|
249
|
+
**示例**:
|
|
250
|
+
```java
|
|
251
|
+
@WebMvcTest(UserController.class)
|
|
252
|
+
@Transactional
|
|
253
|
+
class UserControllerTest {
|
|
254
|
+
@Autowired
|
|
255
|
+
private MockMvc mockMvc;
|
|
256
|
+
|
|
257
|
+
@MockBean
|
|
258
|
+
private UserService userService;
|
|
259
|
+
|
|
260
|
+
@Test
|
|
261
|
+
void testCreateUser() throws Exception {
|
|
262
|
+
// Arrange
|
|
263
|
+
User user = new User(1L, "John", "john@example.com");
|
|
264
|
+
when(userService.createUser(any())).thenReturn(user);
|
|
265
|
+
|
|
266
|
+
// Act & Assert
|
|
267
|
+
mockMvc.perform(post("/api/users")
|
|
268
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
269
|
+
.content("{\"name\":\"John\",\"email\":\"john@example.com\"}"))
|
|
270
|
+
.andExpect(status().isOk())
|
|
271
|
+
.andExpect(jsonPath("$.name").value("John"));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 四、隔离级别选择指南
|
|
279
|
+
|
|
280
|
+
| 测试类型 | 隔离级别 | 推荐策略 |
|
|
281
|
+
|---------|---------|---------|
|
|
282
|
+
| **Repository 层** | L1 | H2 内存库 |
|
|
283
|
+
| **Service 层(同步)** | L2 | @Transactional |
|
|
284
|
+
| **Service 层(异步)** | L3 | 手动清理 |
|
|
285
|
+
| **API 层(简单)** | L2 | MockMvc + @Transactional |
|
|
286
|
+
| **API 层(复杂)** | L1 | MockMvc + H2 |
|
|
287
|
+
| **端到端** | L1 | Testcontainers |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 五、常见问题和解决方案
|
|
292
|
+
|
|
293
|
+
### 1. 异步操作测试
|
|
294
|
+
|
|
295
|
+
**问题**:@Transactional 不适用于异步操作(消息队列、异步方法)
|
|
296
|
+
|
|
297
|
+
**解决方案**:
|
|
298
|
+
- 使用 @AfterEach 手动清理
|
|
299
|
+
- 使用 Awaitility 等待异步操作完成
|
|
300
|
+
- 使用 embedded broker 替代 mock
|
|
301
|
+
|
|
302
|
+
### 2. 外部依赖测试
|
|
303
|
+
|
|
304
|
+
**问题**:外部 API 不可用或不稳定
|
|
305
|
+
|
|
306
|
+
**解决方案**:
|
|
307
|
+
- 使用 MockWebServer/WireMock 模拟外部 API
|
|
308
|
+
- 使用 @MockBean mock Feign 客户端
|
|
309
|
+
- 使用 Testcontainers 运行真实服务
|
|
310
|
+
|
|
311
|
+
### 3. 测试数据污染
|
|
312
|
+
|
|
313
|
+
**问题**:测试数据泄漏到其他测试
|
|
314
|
+
|
|
315
|
+
**解决方案**:
|
|
316
|
+
- 每个测试独立数据(Builder 模式)
|
|
317
|
+
- 使用 @Transactional 自动回滚
|
|
318
|
+
- 使用 @DirtContexts 标记需要重新加载上下文的测试
|
|
319
|
+
|
|
320
|
+
### 4. 测试速度慢
|
|
321
|
+
|
|
322
|
+
**问题**:Testcontainers 启动慢
|
|
323
|
+
|
|
324
|
+
**解决方案**:
|
|
325
|
+
- 使用 @Testcontainers 的 reuse 模式
|
|
326
|
+
- 使用 H2 替代真实数据库(Repository 层)
|
|
327
|
+
- 使用 mock 替代真实外部服务
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 六、与 glaf4-test 的关系
|
|
332
|
+
|
|
333
|
+
本隔离策略参考了 glaf4-test 的以下规范:
|
|
334
|
+
- **H2 替代 MySQL**:`MERGE INTO ... KEY(id)` 幂等写法、`MODE=MySQL;DATABASE_TO_UPPER=false`
|
|
335
|
+
- **rabbitmq-mock**:`MockConnectionFactory → CachingConnectionFactory → firstRabbitTemplate`
|
|
336
|
+
- **embedded-redis**:端口 6378、`@AutoConfigureBefore` Redisson config
|
|
337
|
+
|
|
338
|
+
**注意**:glaf4-test 的部分配置是 GLAF4 框架专用的(如 `gtmc.glaf4.*` 配置键),本规范已抽象为通用模式。
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 变更记录
|
|
343
|
+
|
|
344
|
+
| 日期 | 版本 | 变更内容 |
|
|
345
|
+
|------|------|---------|
|
|
346
|
+
| 2026-08-04 | v1.0 | 从 glaf4-test h2-rabbitmq-redis.md 抽取通用隔离策略 |
|