@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,415 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spring-patterns
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
updated_at: 2026-08-04
|
|
5
|
+
source: team-flow plugin (glaf4-test compliant)
|
|
6
|
+
description: Spring Boot 测试规范,符合 glaf4-test 要求
|
|
7
|
+
tech_stack: [java, spring-boot]
|
|
8
|
+
glaf4_compliant: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Spring Boot 测试规范(glaf4-test 兼容)
|
|
12
|
+
|
|
13
|
+
> 本规范符合 glaf4-test 的 Spring Boot 测试要求
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. MockMvc 三模式
|
|
18
|
+
|
|
19
|
+
### standaloneSetup(推荐用于 Controller 单元测试)
|
|
20
|
+
|
|
21
|
+
```java
|
|
22
|
+
class UserControllerTest {
|
|
23
|
+
|
|
24
|
+
private MockMvc mockMvc;
|
|
25
|
+
|
|
26
|
+
@Mock
|
|
27
|
+
private UserService userService;
|
|
28
|
+
|
|
29
|
+
@BeforeEach
|
|
30
|
+
void setup() {
|
|
31
|
+
mockMvc = MockMvcBuilders.standaloneSetup(new UserController(userService)).build();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
void testGetUser() throws Exception {
|
|
36
|
+
when(userService.getUser(1L)).thenReturn(new User("John"));
|
|
37
|
+
|
|
38
|
+
mockMvc.perform(get("/api/users/1"))
|
|
39
|
+
.andExpect(status().isOk())
|
|
40
|
+
.andExpect(jsonPath("$.name").value("John"));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**适用场景**:
|
|
46
|
+
- 只测试 Controller 层
|
|
47
|
+
- 不加载 Spring 上下文
|
|
48
|
+
- 速度快,隔离性好
|
|
49
|
+
|
|
50
|
+
### @WebMvcTest(推荐用于 Controller 集成测试)
|
|
51
|
+
|
|
52
|
+
```java
|
|
53
|
+
@WebMvcTest(UserController.class)
|
|
54
|
+
class UserControllerIntegrationTest {
|
|
55
|
+
|
|
56
|
+
@Autowired
|
|
57
|
+
private MockMvc mockMvc;
|
|
58
|
+
|
|
59
|
+
@MockBean
|
|
60
|
+
private UserService userService;
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
void testGetUser() throws Exception {
|
|
64
|
+
when(userService.getUser(1L)).thenReturn(new User("John"));
|
|
65
|
+
|
|
66
|
+
mockMvc.perform(get("/api/users/1"))
|
|
67
|
+
.andExpect(status().isOk())
|
|
68
|
+
.andExpect(jsonPath("$.name").value("John"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**适用场景**:
|
|
74
|
+
- 测试 Controller + Spring MVC 配置
|
|
75
|
+
- 自动配置 MockMvc
|
|
76
|
+
- 只加载 Web 层
|
|
77
|
+
|
|
78
|
+
### @SpringBootTest + @AutoConfigureMockMvc(全上下文测试)
|
|
79
|
+
|
|
80
|
+
```java
|
|
81
|
+
@SpringBootTest
|
|
82
|
+
@AutoConfigureMockMvc
|
|
83
|
+
class UserControllerE2ETest {
|
|
84
|
+
|
|
85
|
+
@Autowired
|
|
86
|
+
private MockMvc mockMvc;
|
|
87
|
+
|
|
88
|
+
@Autowired
|
|
89
|
+
private UserRepository userRepository;
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
void testGetUser() throws Exception {
|
|
93
|
+
// Arrange
|
|
94
|
+
User user = userRepository.save(new User("John"));
|
|
95
|
+
|
|
96
|
+
// Act & Assert
|
|
97
|
+
mockMvc.perform(get("/api/users/" + user.getId()))
|
|
98
|
+
.andExpect(status().isOk())
|
|
99
|
+
.andExpect(jsonPath("$.name").value("John"));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**适用场景**:
|
|
105
|
+
- 端到端测试
|
|
106
|
+
- 加载完整 Spring 上下文
|
|
107
|
+
- 速度慢,但测试最全面
|
|
108
|
+
|
|
109
|
+
### 选择指南
|
|
110
|
+
|
|
111
|
+
| 模式 | 加载范围 | 速度 | 适用场景 |
|
|
112
|
+
|------|---------|------|---------|
|
|
113
|
+
| standaloneSetup | 只加载 Controller | 最快 | Controller 单元测试 |
|
|
114
|
+
| @WebMvcTest | Web 层 | 快 | Controller 集成测试 |
|
|
115
|
+
| @SpringBootTest | 全上下文 | 慢 | 端到端测试 |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 2. @Transactional 测试
|
|
120
|
+
|
|
121
|
+
### 同步操作
|
|
122
|
+
|
|
123
|
+
```java
|
|
124
|
+
@SpringBootTest
|
|
125
|
+
@Transactional
|
|
126
|
+
class UserServiceTest {
|
|
127
|
+
|
|
128
|
+
@Autowired
|
|
129
|
+
private UserService userService;
|
|
130
|
+
|
|
131
|
+
@Autowired
|
|
132
|
+
private UserRepository userRepository;
|
|
133
|
+
|
|
134
|
+
@Test
|
|
135
|
+
void testCreateUser() {
|
|
136
|
+
// Arrange
|
|
137
|
+
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
User user = userService.createUser(request);
|
|
141
|
+
|
|
142
|
+
// Assert
|
|
143
|
+
assertNotNull(user.getId());
|
|
144
|
+
assertEquals("John", user.getName());
|
|
145
|
+
}
|
|
146
|
+
// 测试结束后自动回滚
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 异步操作(不适用 @Transactional)
|
|
151
|
+
|
|
152
|
+
```java
|
|
153
|
+
@SpringBootTest
|
|
154
|
+
class UserServiceAsyncTest {
|
|
155
|
+
|
|
156
|
+
@Autowired
|
|
157
|
+
private UserService userService;
|
|
158
|
+
|
|
159
|
+
@Autowired
|
|
160
|
+
private RabbitTemplate rabbitTemplate;
|
|
161
|
+
|
|
162
|
+
@AfterEach
|
|
163
|
+
void cleanup() {
|
|
164
|
+
// 手动清空消息队列
|
|
165
|
+
rabbitTemplate.execute(channel -> {
|
|
166
|
+
channel.queuePurge("user-events");
|
|
167
|
+
return null;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@Test
|
|
172
|
+
void testCreateUserSendsEvent() {
|
|
173
|
+
// Arrange
|
|
174
|
+
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
User user = userService.createUser(request);
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
assertNotNull(user.getId());
|
|
181
|
+
|
|
182
|
+
// 验证消息发送
|
|
183
|
+
Message message = rabbitTemplate.receive("user-events", 1000);
|
|
184
|
+
assertNotNull(message);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 3. 测试隔离
|
|
192
|
+
|
|
193
|
+
### Repository 层:H2 内存库
|
|
194
|
+
|
|
195
|
+
```java
|
|
196
|
+
@DataJpaTest
|
|
197
|
+
class UserRepositoryTest {
|
|
198
|
+
|
|
199
|
+
@Autowired
|
|
200
|
+
private UserRepository userRepository;
|
|
201
|
+
|
|
202
|
+
@Test
|
|
203
|
+
void testFindByEmail() {
|
|
204
|
+
// Arrange
|
|
205
|
+
User user = new User("John", "john@example.com");
|
|
206
|
+
userRepository.save(user);
|
|
207
|
+
|
|
208
|
+
// Act
|
|
209
|
+
Optional<User> found = userRepository.findByEmail("john@example.com");
|
|
210
|
+
|
|
211
|
+
// Assert
|
|
212
|
+
assertTrue(found.isPresent());
|
|
213
|
+
assertEquals("John", found.get().getName());
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**H2 配置**(application-test.yml):
|
|
219
|
+
```yaml
|
|
220
|
+
spring:
|
|
221
|
+
datasource:
|
|
222
|
+
url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_UPPER=false
|
|
223
|
+
driver-class-name: org.h2.Driver
|
|
224
|
+
jpa:
|
|
225
|
+
hibernate:
|
|
226
|
+
ddl-auto: create-drop
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Service 层:@Transactional 或手动清理
|
|
230
|
+
|
|
231
|
+
**同步操作**:
|
|
232
|
+
```java
|
|
233
|
+
@SpringBootTest
|
|
234
|
+
@Transactional
|
|
235
|
+
class UserServiceTest {
|
|
236
|
+
// ...
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**异步操作**:
|
|
241
|
+
```java
|
|
242
|
+
@SpringBootTest
|
|
243
|
+
class UserServiceAsyncTest {
|
|
244
|
+
@AfterEach
|
|
245
|
+
void cleanup() {
|
|
246
|
+
// 手动清理消息队列、缓存等
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### API 层:MockMvc + @Transactional
|
|
252
|
+
|
|
253
|
+
```java
|
|
254
|
+
@WebMvcTest(UserController.class)
|
|
255
|
+
@Transactional
|
|
256
|
+
class UserControllerTest {
|
|
257
|
+
// ...
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 4. Mock 规范
|
|
264
|
+
|
|
265
|
+
### @MockBean 使用规范
|
|
266
|
+
|
|
267
|
+
```java
|
|
268
|
+
@WebMvcTest(UserController.class)
|
|
269
|
+
class UserControllerTest {
|
|
270
|
+
|
|
271
|
+
@MockBean
|
|
272
|
+
private UserService userService;
|
|
273
|
+
|
|
274
|
+
@MockBean
|
|
275
|
+
private EmailService emailService;
|
|
276
|
+
|
|
277
|
+
// ...
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**规则**:
|
|
282
|
+
- ✅ 只 mock 外部依赖(外部 API、第三方服务)
|
|
283
|
+
- ✅ 内部依赖尽量用真实实现
|
|
284
|
+
- ❌ 不要过度 mock(会导致测试失去意义)
|
|
285
|
+
|
|
286
|
+
### @SpyBean 使用规范
|
|
287
|
+
|
|
288
|
+
```java
|
|
289
|
+
@SpringBootTest
|
|
290
|
+
class UserServiceTest {
|
|
291
|
+
|
|
292
|
+
@SpyBean
|
|
293
|
+
private UserService userService;
|
|
294
|
+
|
|
295
|
+
@Test
|
|
296
|
+
void testCreateUser() {
|
|
297
|
+
// Arrange
|
|
298
|
+
doReturn(new User("John")).when(userService).generateUser(any());
|
|
299
|
+
|
|
300
|
+
// Act
|
|
301
|
+
User user = userService.createUser(new CreateUserRequest("John"));
|
|
302
|
+
|
|
303
|
+
// Assert
|
|
304
|
+
assertEquals("John", user.getName());
|
|
305
|
+
verify(userService).generateUser(any());
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**适用场景**:
|
|
311
|
+
- 需要部分 mock(只 mock 某个方法)
|
|
312
|
+
- 需要验证方法调用次数
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 5. 测试配置
|
|
317
|
+
|
|
318
|
+
### application-test.yml
|
|
319
|
+
|
|
320
|
+
```yaml
|
|
321
|
+
spring:
|
|
322
|
+
# 数据库
|
|
323
|
+
datasource:
|
|
324
|
+
url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_UPPER=false
|
|
325
|
+
driver-class-name: org.h2.Driver
|
|
326
|
+
jpa:
|
|
327
|
+
hibernate:
|
|
328
|
+
ddl-auto: create-drop
|
|
329
|
+
|
|
330
|
+
# 消息队列
|
|
331
|
+
rabbitmq:
|
|
332
|
+
host: localhost
|
|
333
|
+
port: 5672
|
|
334
|
+
username: guest
|
|
335
|
+
password: guest
|
|
336
|
+
|
|
337
|
+
# Redis
|
|
338
|
+
redis:
|
|
339
|
+
host: localhost
|
|
340
|
+
port: 6378
|
|
341
|
+
|
|
342
|
+
# 日志
|
|
343
|
+
logging:
|
|
344
|
+
level:
|
|
345
|
+
root: INFO
|
|
346
|
+
com.example: DEBUG
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 测试 Profile
|
|
350
|
+
|
|
351
|
+
```java
|
|
352
|
+
@SpringBootTest
|
|
353
|
+
@ActiveProfiles("test")
|
|
354
|
+
class UserServiceTest {
|
|
355
|
+
// ...
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 6. 常见问题
|
|
362
|
+
|
|
363
|
+
### 1. @Transactional 不回滚
|
|
364
|
+
|
|
365
|
+
**原因**:
|
|
366
|
+
- 异步操作(@Async、消息队列)
|
|
367
|
+
- 多数据源
|
|
368
|
+
- 自定义事务管理器
|
|
369
|
+
|
|
370
|
+
**解决方案**:
|
|
371
|
+
- 异步操作:手动清理
|
|
372
|
+
- 多数据源:@Transactional("transactionManagerName")
|
|
373
|
+
- 自定义事务管理器:确保测试使用正确的管理器
|
|
374
|
+
|
|
375
|
+
### 2. @MockBean 影响其他测试
|
|
376
|
+
|
|
377
|
+
**原因**:
|
|
378
|
+
- @MockBean 会重新加载 Spring 上下文
|
|
379
|
+
- 大量使用 @MockBean 会导致测试变慢
|
|
380
|
+
|
|
381
|
+
**解决方案**:
|
|
382
|
+
- 使用 standaloneSetup 替代 @WebMvcTest
|
|
383
|
+
- 使用 @SpyBean 替代 @MockBean(部分 mock)
|
|
384
|
+
- 合并相似的测试用例
|
|
385
|
+
|
|
386
|
+
### 3. 测试数据污染
|
|
387
|
+
|
|
388
|
+
**原因**:
|
|
389
|
+
- 测试数据泄漏到其他测试
|
|
390
|
+
- 数据库状态不一致
|
|
391
|
+
|
|
392
|
+
**解决方案**:
|
|
393
|
+
- 使用 @Transactional 自动回滚
|
|
394
|
+
- 使用 @DirtContexts 标记需要重新加载上下文的测试
|
|
395
|
+
- 每个测试独立数据(Builder 模式)
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 7. 与 glaf4-test 的关系
|
|
400
|
+
|
|
401
|
+
本规范参考了 glaf4-test 的以下要求:
|
|
402
|
+
- MockMvc 三模式:standaloneSetup / @WebMvcTest / @SpringBootTest
|
|
403
|
+
- H2 内存库:`MODE=MySQL;DATABASE_TO_UPPER=false`
|
|
404
|
+
- @Transactional 测试:同步操作自动回滚
|
|
405
|
+
- @MockBean 规范:只 mock 外部依赖
|
|
406
|
+
|
|
407
|
+
**注意**:glaf4-test 的部分配置是 GLAF4 框架专用的(如 `gtmc.glaf4.*` 配置键),本规范已抽象为通用模式。
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 变更记录
|
|
412
|
+
|
|
413
|
+
| 日期 | 版本 | 变更内容 |
|
|
414
|
+
|------|------|---------|
|
|
415
|
+
| 2026-08-04 | v1.0 | 初始版本,符合 glaf4-test 要求 |
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: js-testing
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
updated_at: 2026-08-04
|
|
5
|
+
source: team-flow plugin
|
|
6
|
+
description: JavaScript 测试规范(Jest/Vitest)
|
|
7
|
+
tech_stack: [javascript, jest, vitest]
|
|
8
|
+
glaf4_compliant: false
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# JavaScript 测试规范
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. 测试结构
|
|
16
|
+
|
|
17
|
+
### Jest / Vitest
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
describe('UserService', () => {
|
|
21
|
+
describe('getUser', () => {
|
|
22
|
+
it('should return user when id exists', async () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const userId = 1;
|
|
25
|
+
const expectedUser = { id: 1, name: 'John' };
|
|
26
|
+
userRepository.findById.mockResolvedValue(expectedUser);
|
|
27
|
+
|
|
28
|
+
// Act
|
|
29
|
+
const user = await userService.getUser(userId);
|
|
30
|
+
|
|
31
|
+
// Assert
|
|
32
|
+
expect(user).toEqual(expectedUser);
|
|
33
|
+
expect(userRepository.findById).toHaveBeenCalledWith(userId);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error when id not found', async () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const userId = 999;
|
|
39
|
+
userRepository.findById.mockResolvedValue(null);
|
|
40
|
+
|
|
41
|
+
// Act & Assert
|
|
42
|
+
await expect(userService.getUser(userId)).rejects.toThrow('User not found');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Mock 规范
|
|
51
|
+
|
|
52
|
+
### Jest
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// 自动 mock
|
|
56
|
+
jest.mock('./userRepository');
|
|
57
|
+
|
|
58
|
+
// 手动 mock
|
|
59
|
+
const mockUserRepository = {
|
|
60
|
+
findById: jest.fn(),
|
|
61
|
+
save: jest.fn(),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// mock 实现
|
|
65
|
+
mockUserRepository.findById.mockResolvedValue({ id: 1, name: 'John' });
|
|
66
|
+
|
|
67
|
+
// 验证调用
|
|
68
|
+
expect(mockUserRepository.findById).toHaveBeenCalledWith(1);
|
|
69
|
+
expect(mockUserRepository.findById).toHaveBeenCalledTimes(1);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Vitest
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
import { vi } from 'vitest';
|
|
76
|
+
|
|
77
|
+
// 自动 mock
|
|
78
|
+
vi.mock('./userRepository');
|
|
79
|
+
|
|
80
|
+
// 手动 mock
|
|
81
|
+
const mockUserRepository = {
|
|
82
|
+
findById: vi.fn(),
|
|
83
|
+
save: vi.fn(),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// mock 实现
|
|
87
|
+
mockUserRepository.findById.mockResolvedValue({ id: 1, name: 'John' });
|
|
88
|
+
|
|
89
|
+
// 验证调用
|
|
90
|
+
expect(mockUserRepository.findById).toHaveBeenCalledWith(1);
|
|
91
|
+
expect(mockUserRepository.findById).toHaveBeenCalledTimes(1);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 3. 断言规范
|
|
97
|
+
|
|
98
|
+
### expect 常用方法
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// 相等
|
|
102
|
+
expect(value).toBe(expected);
|
|
103
|
+
expect(value).toEqual(expected);
|
|
104
|
+
|
|
105
|
+
// 真假
|
|
106
|
+
expect(value).toBeTruthy();
|
|
107
|
+
expect(value).toBeFalsy();
|
|
108
|
+
expect(value).toBeNull();
|
|
109
|
+
expect(value).toBeUndefined();
|
|
110
|
+
|
|
111
|
+
// 数字
|
|
112
|
+
expect(value).toBeGreaterThan(0);
|
|
113
|
+
expect(value).toBeLessThan(100);
|
|
114
|
+
|
|
115
|
+
// 字符串
|
|
116
|
+
expect(value).toMatch(/pattern/);
|
|
117
|
+
expect(value).toContain('substring');
|
|
118
|
+
|
|
119
|
+
// 数组
|
|
120
|
+
expect(array).toContain(item);
|
|
121
|
+
expect(array).toHaveLength(3);
|
|
122
|
+
|
|
123
|
+
// 对象
|
|
124
|
+
expect(object).toHaveProperty('key', 'value');
|
|
125
|
+
|
|
126
|
+
// 异常
|
|
127
|
+
expect(() => fn()).toThrow('error message');
|
|
128
|
+
await expect(promise).rejects.toThrow('error message');
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 禁止无意义断言
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// ❌ 违规
|
|
135
|
+
test('test something', () => {
|
|
136
|
+
expect(true).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ✅ 正确
|
|
140
|
+
test('should return user', () => {
|
|
141
|
+
const user = getUser(1);
|
|
142
|
+
expect(user).toEqual({ id: 1, name: 'John' });
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 4. 异步测试
|
|
149
|
+
|
|
150
|
+
### async/await
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
test('should fetch user', async () => {
|
|
154
|
+
const user = await userService.getUser(1);
|
|
155
|
+
expect(user).toEqual({ id: 1, name: 'John' });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should throw error', async () => {
|
|
159
|
+
await expect(userService.getUser(999)).rejects.toThrow('User not found');
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 回调
|
|
164
|
+
|
|
165
|
+
test('should call callback', (done) => {
|
|
166
|
+
userService.getUser(1, (user) => {
|
|
167
|
+
expect(user).toEqual({ id: 1, name: 'John' });
|
|
168
|
+
done();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5. 测试数据
|
|
176
|
+
|
|
177
|
+
### Builder 模式
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
const userBuilder = (overrides = {}) => ({
|
|
181
|
+
id: 1,
|
|
182
|
+
name: 'John',
|
|
183
|
+
email: 'john@example.com',
|
|
184
|
+
...overrides,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('should create user', () => {
|
|
188
|
+
const userData = userBuilder({ name: 'Jane' });
|
|
189
|
+
const user = await userService.createUser(userData);
|
|
190
|
+
expect(user.name).toBe('Jane');
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Fixture
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
// fixtures/users.json
|
|
198
|
+
[
|
|
199
|
+
{ "id": 1, "name": "John" },
|
|
200
|
+
{ "id": 2, "name": "Jane" }
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
// test
|
|
204
|
+
import users from './fixtures/users.json';
|
|
205
|
+
|
|
206
|
+
test('should return all users', () => {
|
|
207
|
+
const result = userService.getAllUsers();
|
|
208
|
+
expect(result).toEqual(users);
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 6. 测试隔离
|
|
215
|
+
|
|
216
|
+
### 每个测试独立
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
describe('UserService', () => {
|
|
220
|
+
let userService;
|
|
221
|
+
let mockUserRepository;
|
|
222
|
+
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
mockUserRepository = {
|
|
225
|
+
findById: jest.fn(),
|
|
226
|
+
save: jest.fn(),
|
|
227
|
+
};
|
|
228
|
+
userService = new UserService(mockUserRepository);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
afterEach(() => {
|
|
232
|
+
jest.clearAllMocks();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('should return user', async () => {
|
|
236
|
+
mockUserRepository.findById.mockResolvedValue({ id: 1, name: 'John' });
|
|
237
|
+
const user = await userService.getUser(1);
|
|
238
|
+
expect(user).toEqual({ id: 1, name: 'John' });
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 7. 测试质量规则
|
|
246
|
+
|
|
247
|
+
参见 `references/test-quality-rules.md`,主要包括:
|
|
248
|
+
|
|
249
|
+
1. 断言质量规则(missing-meaningful-assertion、weak-assertion-only)
|
|
250
|
+
2. 调试代码残留规则(console.log)
|
|
251
|
+
3. 测试状态规则(test.skip、test.todo)
|
|
252
|
+
4. 测试数据规则(hardcoded-sample-like-value、real-external-url)
|
|
253
|
+
5. 测试结构规则(large-test-file、generic-test-name)
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 变更记录
|
|
258
|
+
|
|
259
|
+
| 日期 | 版本 | 变更内容 |
|
|
260
|
+
|------|------|---------|
|
|
261
|
+
| 2026-08-04 | v1.0 | 初始版本 |
|