@llryiop/avatar-boot-cli 1.0.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.
Files changed (77) hide show
  1. package/README.md +309 -0
  2. package/bin/cli.js +3 -0
  3. package/docs/plans/2026-03-12-avatar-boot-cli-design.md +73 -0
  4. package/docs/plans/2026-03-12-avatar-boot-cli-plan.md +681 -0
  5. package/package.json +28 -0
  6. package/src/index.js +78 -0
  7. package/src/prompts.js +78 -0
  8. package/src/template.js +37 -0
  9. package/src/transform.js +172 -0
  10. package/src/utils.js +34 -0
  11. package/templates/.claude/rules/architecture-redlines.md +146 -0
  12. package/templates/.claude/rules/code-review-standards.md +137 -0
  13. package/templates/.claude/rules/coding-standards.md +56 -0
  14. package/templates/.claude/rules/git-commit.md +59 -0
  15. package/templates/.claude/rules/layered-architecture.md +201 -0
  16. package/templates/.claude/rules/mybatis-plus.md +263 -0
  17. package/templates/.claude/rules/tech-stack.md +41 -0
  18. package/templates/.claude/rules/version.md +467 -0
  19. package/templates/.claude/settings.local.json +18 -0
  20. package/templates/.claude/skills/ai-tool-guide/SKILL.md +314 -0
  21. package/templates/.claude/skills/api-design/SKILL.md +200 -0
  22. package/templates/.claude/skills/api-doc-generator/SKILL.md +380 -0
  23. package/templates/.claude/skills/api-service-module-creator/SKILL.md +1114 -0
  24. package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +243 -0
  25. package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +437 -0
  26. package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +580 -0
  27. package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +572 -0
  28. package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +901 -0
  29. package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +594 -0
  30. package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +586 -0
  31. package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +662 -0
  32. package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +1007 -0
  33. package/templates/.claude/skills/changelog-generator/SKILL.md +114 -0
  34. package/templates/.claude/skills/code-review/SKILL.md +239 -0
  35. package/templates/.claude/skills/crud-generator/SKILL.md +824 -0
  36. package/templates/.claude/skills/database-design/SKILL.md +377 -0
  37. package/templates/.claude/skills/deployment-config/SKILL.md +277 -0
  38. package/templates/.claude/skills/incident-analysis/SKILL.md +241 -0
  39. package/templates/.claude/skills/integration-test-generator/SKILL.md +496 -0
  40. package/templates/.claude/skills/prompt-engineering/SKILL.md +249 -0
  41. package/templates/.claude/skills/requirement-management/SKILL.md +244 -0
  42. package/templates/.claude/skills/security-audit/SKILL.md +330 -0
  43. package/templates/.claude/skills/test-case-design/SKILL.md +257 -0
  44. package/templates/.claude/skills/testing-workflow/SKILL.md +68 -0
  45. package/templates/.claude/skills/troubleshooting/SKILL.md +240 -0
  46. package/templates/CLAUDE.md +173 -0
  47. package/templates/README.md +303 -0
  48. package/templates/avatar-scaffold-api/pom.xml +41 -0
  49. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +40 -0
  50. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/constant/LoginConstant.java +21 -0
  51. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/LoginRequest.java +17 -0
  52. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/RefreshTokenRequest.java +14 -0
  53. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/LoginResponse.java +31 -0
  54. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/TokenInfoResponse.java +25 -0
  55. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/enums/LoginTypeEnum.java +23 -0
  56. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginException.java +23 -0
  57. package/templates/avatar-scaffold-service/k8s-app/Dockerfile +14 -0
  58. package/templates/avatar-scaffold-service/k8s-app/Dockerfile-arm64 +14 -0
  59. package/templates/avatar-scaffold-service/packaging/assembly.xml +16 -0
  60. package/templates/avatar-scaffold-service/pom.xml +150 -0
  61. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/Application.java +21 -0
  62. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/config/LoginConfig.java +20 -0
  63. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/controller/LoginController.java +37 -0
  64. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/converter/LoginConverter.java +54 -0
  65. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +21 -0
  66. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserLoginEntity.java +33 -0
  67. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserTokenEntity.java +39 -0
  68. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +20 -0
  69. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/LoginService.java +22 -0
  70. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/impl/LoginServiceImpl.java +43 -0
  71. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/utils/LoginUtils.java +31 -0
  72. package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +29 -0
  73. package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +61 -0
  74. package/templates/avatar-scaffold-service/src/main/resources/application-prod.yaml +28 -0
  75. package/templates/avatar-scaffold-service/src/main/resources/application-test.yaml +28 -0
  76. package/templates/avatar-scaffold-service/src/main/resources/application.yaml +12 -0
  77. package/templates/pom.xml +98 -0
@@ -0,0 +1,496 @@
1
+ ---
2
+ name: integration-test-generator
3
+ description: |
4
+ 集成测试生成技能。当用户提到以下关键词时触发:
5
+ integration test, 集成测试, SpringBootTest, MockMvc,
6
+ TestContainers, 端到端测试, E2E test, 接口测试
7
+
8
+ 本技能提供 Spring Boot 集成测试的完整模板,包括 @SpringBootTest 配置、
9
+ MockMvc 控制器测试、@MockBean 服务模拟、TestContainers 外部依赖集成、
10
+ 测试数据管理和断言模式等。
11
+ ---
12
+
13
+ # 集成测试生成技能
14
+
15
+ ## 适用场景
16
+
17
+ - 为 Controller 层生成集成测试
18
+ - 测试接口的完整请求-响应链路
19
+ - 使用 TestContainers 测试数据库/缓存集成
20
+ - 验证 Spring 上下文的正确装配
21
+
22
+ ## @SpringBootTest 基础模板
23
+
24
+ ### 最小配置
25
+
26
+ ```java
27
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
28
+ @AutoConfigureMockMvc
29
+ @ActiveProfiles("test")
30
+ class UserControllerIntegrationTest {
31
+
32
+ @Autowired
33
+ private MockMvc mockMvc;
34
+
35
+ @Autowired
36
+ private ObjectMapper objectMapper;
37
+
38
+ @Test
39
+ @DisplayName("创建用户集成测试 - 完整链路")
40
+ void should_create_user_successfully() throws Exception {
41
+ // Given
42
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
43
+ dto.setUsername("integration_test_user");
44
+ dto.setPhone("13812345678");
45
+ dto.setEmail("test@example.com");
46
+
47
+ // When & Then
48
+ mockMvc.perform(post("/api/users")
49
+ .contentType(MediaType.APPLICATION_JSON)
50
+ .content(objectMapper.writeValueAsString(dto)))
51
+ .andExpect(status().isOk())
52
+ .andExpect(jsonPath("$.code").value(0))
53
+ .andExpect(jsonPath("$.data").isNumber());
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### 测试配置文件
59
+
60
+ ```yaml
61
+ # src/test/resources/application-test.yml
62
+ spring:
63
+ datasource:
64
+ url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL
65
+ driver-class-name: org.h2.Driver
66
+ username: sa
67
+ password:
68
+ sql:
69
+ init:
70
+ mode: always
71
+ schema-locations: classpath:schema-test.sql
72
+
73
+ mybatis-plus:
74
+ mapper-locations: classpath*:/mapper/**/*.xml
75
+ configuration:
76
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
77
+ ```
78
+
79
+ ## MockMvc Controller 测试
80
+
81
+ ### GET 请求测试
82
+
83
+ ```java
84
+ @Test
85
+ @DisplayName("根据ID查询用户 - 用户存在时返回用户信息")
86
+ void should_return_user_when_id_exists() throws Exception {
87
+ // Given - 预置测试数据
88
+ Long userId = prepareTestUser("testuser", "13800000001");
89
+
90
+ // When & Then
91
+ mockMvc.perform(get("/api/users/{id}", userId)
92
+ .accept(MediaType.APPLICATION_JSON))
93
+ .andExpect(status().isOk())
94
+ .andExpect(jsonPath("$.code").value(0))
95
+ .andExpect(jsonPath("$.data.username").value("testuser"))
96
+ .andExpect(jsonPath("$.data.phone").value("138****0001"))
97
+ .andDo(print()); // 打印请求响应详情,便于调试
98
+ }
99
+
100
+ @Test
101
+ @DisplayName("根据ID查询用户 - 用户不存在时返回错误")
102
+ void should_return_error_when_id_not_exists() throws Exception {
103
+ mockMvc.perform(get("/api/users/{id}", 999999L)
104
+ .accept(MediaType.APPLICATION_JSON))
105
+ .andExpect(status().isOk())
106
+ .andExpect(jsonPath("$.code").value(not(0)))
107
+ .andExpect(jsonPath("$.message").isNotEmpty());
108
+ }
109
+ ```
110
+
111
+ ### POST 请求测试
112
+
113
+ ```java
114
+ @Test
115
+ @DisplayName("创建用户 - 参数校验失败时返回400")
116
+ void should_return_400_when_validation_fails() throws Exception {
117
+ // Given - 缺少必填字段
118
+ UserInfoCreateDTO dto = new UserInfoCreateDTO();
119
+ dto.setUsername(""); // 用户名为空
120
+
121
+ // When & Then
122
+ mockMvc.perform(post("/api/users")
123
+ .contentType(MediaType.APPLICATION_JSON)
124
+ .content(objectMapper.writeValueAsString(dto)))
125
+ .andExpect(status().isBadRequest());
126
+ }
127
+ ```
128
+
129
+ ### 分页查询测试
130
+
131
+ ```java
132
+ @Test
133
+ @DisplayName("分页查询用户列表")
134
+ void should_return_paged_users() throws Exception {
135
+ // Given - 准备多条测试数据
136
+ for (int i = 0; i < 15; i++) {
137
+ prepareTestUser("user_" + i, "1380000" + String.format("%04d", i));
138
+ }
139
+
140
+ UserInfoPageQueryDTO queryDTO = new UserInfoPageQueryDTO();
141
+ queryDTO.setPageNum(1);
142
+ queryDTO.setPageSize(10);
143
+
144
+ // When & Then
145
+ mockMvc.perform(post("/api/users/page")
146
+ .contentType(MediaType.APPLICATION_JSON)
147
+ .content(objectMapper.writeValueAsString(queryDTO)))
148
+ .andExpect(status().isOk())
149
+ .andExpect(jsonPath("$.code").value(0))
150
+ .andExpect(jsonPath("$.data.records").isArray())
151
+ .andExpect(jsonPath("$.data.records.length()").value(10))
152
+ .andExpect(jsonPath("$.data.total").value(15))
153
+ .andExpect(jsonPath("$.data.pages").value(2));
154
+ }
155
+ ```
156
+
157
+ ### PUT 请求测试
158
+
159
+ ```java
160
+ @Test
161
+ @DisplayName("更新用户信息")
162
+ void should_update_user_successfully() throws Exception {
163
+ // Given
164
+ Long userId = prepareTestUser("old_name", "13800000001");
165
+ UserInfoUpdateDTO dto = new UserInfoUpdateDTO();
166
+ dto.setUsername("new_name");
167
+ dto.setEmail("new@example.com");
168
+
169
+ // When & Then
170
+ mockMvc.perform(put("/api/users/{id}", userId)
171
+ .contentType(MediaType.APPLICATION_JSON)
172
+ .content(objectMapper.writeValueAsString(dto)))
173
+ .andExpect(status().isOk())
174
+ .andExpect(jsonPath("$.code").value(0));
175
+
176
+ // 验证数据库中已更新
177
+ mockMvc.perform(get("/api/users/{id}", userId))
178
+ .andExpect(jsonPath("$.data.username").value("new_name"))
179
+ .andExpect(jsonPath("$.data.email").value("new@example.com"));
180
+ }
181
+ ```
182
+
183
+ ### DELETE 请求测试
184
+
185
+ ```java
186
+ @Test
187
+ @DisplayName("删除用户")
188
+ void should_delete_user_successfully() throws Exception {
189
+ // Given
190
+ Long userId = prepareTestUser("to_delete", "13800000001");
191
+
192
+ // When
193
+ mockMvc.perform(delete("/api/users/{id}", userId))
194
+ .andExpect(status().isOk())
195
+ .andExpect(jsonPath("$.code").value(0));
196
+
197
+ // Then - 验证已删除
198
+ mockMvc.perform(get("/api/users/{id}", userId))
199
+ .andExpect(jsonPath("$.code").value(not(0)));
200
+ }
201
+ ```
202
+
203
+ ## @MockBean 服务模拟
204
+
205
+ 当需要隔离外部依赖(如第三方服务调用)时使用 `@MockBean`:
206
+
207
+ ```java
208
+ @SpringBootTest
209
+ @AutoConfigureMockMvc
210
+ @ActiveProfiles("test")
211
+ class OrderControllerIntegrationTest {
212
+
213
+ @Autowired
214
+ private MockMvc mockMvc;
215
+
216
+ @Autowired
217
+ private ObjectMapper objectMapper;
218
+
219
+ @MockBean
220
+ private PaymentFeignClient paymentFeignClient;
221
+
222
+ @MockBean
223
+ private SmsService smsService;
224
+
225
+ @Test
226
+ @DisplayName("创建订单 - 模拟支付服务成功")
227
+ void should_create_order_when_payment_succeeds() throws Exception {
228
+ // Given - 模拟外部服务
229
+ when(paymentFeignClient.createPayment(any()))
230
+ .thenReturn(Result.success(new PaymentVO("PAY202301010001")));
231
+ doNothing().when(smsService).sendOrderNotification(any());
232
+
233
+ OrderCreateDTO dto = new OrderCreateDTO();
234
+ dto.setProductId(1L);
235
+ dto.setQuantity(2);
236
+
237
+ // When & Then
238
+ mockMvc.perform(post("/api/orders")
239
+ .contentType(MediaType.APPLICATION_JSON)
240
+ .content(objectMapper.writeValueAsString(dto)))
241
+ .andExpect(status().isOk())
242
+ .andExpect(jsonPath("$.code").value(0))
243
+ .andExpect(jsonPath("$.data.orderId").isNotEmpty());
244
+
245
+ // 验证外部服务被调用
246
+ verify(paymentFeignClient, times(1)).createPayment(any());
247
+ verify(smsService, times(1)).sendOrderNotification(any());
248
+ }
249
+
250
+ @Test
251
+ @DisplayName("创建订单 - 支付服务超时时应回滚")
252
+ void should_rollback_when_payment_timeout() throws Exception {
253
+ // Given
254
+ when(paymentFeignClient.createPayment(any()))
255
+ .thenThrow(new FeignException.GatewayTimeout("支付服务超时", null, null, null));
256
+
257
+ OrderCreateDTO dto = new OrderCreateDTO();
258
+ dto.setProductId(1L);
259
+ dto.setQuantity(2);
260
+
261
+ // When & Then
262
+ mockMvc.perform(post("/api/orders")
263
+ .contentType(MediaType.APPLICATION_JSON)
264
+ .content(objectMapper.writeValueAsString(dto)))
265
+ .andExpect(status().isOk())
266
+ .andExpect(jsonPath("$.code").value(not(0)))
267
+ .andExpect(jsonPath("$.message").value(containsString("支付")));
268
+ }
269
+ }
270
+ ```
271
+
272
+ ## TestContainers 集成
273
+
274
+ ### 依赖配置
275
+
276
+ ```xml
277
+ <!-- pom.xml -->
278
+ <dependency>
279
+ <groupId>org.testcontainers</groupId>
280
+ <artifactId>testcontainers</artifactId>
281
+ <scope>test</scope>
282
+ </dependency>
283
+ <dependency>
284
+ <groupId>org.testcontainers</groupId>
285
+ <artifactId>mysql</artifactId>
286
+ <scope>test</scope>
287
+ </dependency>
288
+ <dependency>
289
+ <groupId>org.testcontainers</groupId>
290
+ <artifactId>junit-jupiter</artifactId>
291
+ <scope>test</scope>
292
+ </dependency>
293
+ ```
294
+
295
+ ### MySQL TestContainer
296
+
297
+ ```java
298
+ @SpringBootTest
299
+ @AutoConfigureMockMvc
300
+ @Testcontainers
301
+ class UserRepositoryIntegrationTest {
302
+
303
+ @Container
304
+ static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
305
+ .withDatabaseName("test_db")
306
+ .withUsername("test")
307
+ .withPassword("test")
308
+ .withInitScript("schema-test.sql");
309
+
310
+ @DynamicPropertySource
311
+ static void configureProperties(DynamicPropertyRegistry registry) {
312
+ registry.add("spring.datasource.url", mysql::getJdbcUrl);
313
+ registry.add("spring.datasource.username", mysql::getUsername);
314
+ registry.add("spring.datasource.password", mysql::getPassword);
315
+ }
316
+
317
+ @Autowired
318
+ private UserInfoMapper userInfoMapper;
319
+
320
+ @Test
321
+ @DisplayName("MyBatis Plus CRUD 集成测试")
322
+ void should_crud_user_with_real_mysql() {
323
+ // Given
324
+ UserInfo user = new UserInfo();
325
+ user.setUsername("container_test");
326
+ user.setPhone("13812345678");
327
+
328
+ // When - 插入
329
+ int inserted = userInfoMapper.insert(user);
330
+
331
+ // Then
332
+ assertThat(inserted).isEqualTo(1);
333
+ assertThat(user.getId()).isNotNull();
334
+
335
+ // When - 查询
336
+ UserInfo found = userInfoMapper.selectById(user.getId());
337
+ assertThat(found).isNotNull();
338
+ assertThat(found.getUsername()).isEqualTo("container_test");
339
+ }
340
+ }
341
+ ```
342
+
343
+ ### Redis TestContainer
344
+
345
+ ```java
346
+ @Container
347
+ static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
348
+ .withExposedPorts(6379);
349
+
350
+ @DynamicPropertySource
351
+ static void configureRedis(DynamicPropertyRegistry registry) {
352
+ registry.add("spring.data.redis.host", redis::getHost);
353
+ registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
354
+ }
355
+ ```
356
+
357
+ ## 测试数据管理
358
+
359
+ ### 数据准备与清理
360
+
361
+ ```java
362
+ @SpringBootTest
363
+ @AutoConfigureMockMvc
364
+ @ActiveProfiles("test")
365
+ @Transactional // 每个测试方法执行后自动回滚
366
+ class UserControllerIntegrationTest {
367
+
368
+ @Autowired
369
+ private UserInfoMapper userInfoMapper;
370
+
371
+ /**
372
+ * 准备测试用户数据
373
+ */
374
+ private Long prepareTestUser(String username, String phone) {
375
+ UserInfo user = new UserInfo();
376
+ user.setUsername(username);
377
+ user.setPhone(phone);
378
+ user.setStatus(1);
379
+ user.setCreateTime(LocalDateTime.now());
380
+ userInfoMapper.insert(user);
381
+ return user.getId();
382
+ }
383
+
384
+ /**
385
+ * 批量准备测试数据
386
+ */
387
+ private List<Long> prepareTestUsers(int count) {
388
+ List<Long> ids = new ArrayList<>();
389
+ for (int i = 0; i < count; i++) {
390
+ ids.add(prepareTestUser("user_" + i, "1380000" + String.format("%04d", i)));
391
+ }
392
+ return ids;
393
+ }
394
+ }
395
+ ```
396
+
397
+ ### SQL 脚本初始化
398
+
399
+ ```sql
400
+ -- src/test/resources/schema-test.sql
401
+ CREATE TABLE IF NOT EXISTS user_info (
402
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
403
+ username VARCHAR(50) NOT NULL,
404
+ phone VARCHAR(20),
405
+ email VARCHAR(100),
406
+ status TINYINT DEFAULT 1,
407
+ deleted TINYINT DEFAULT 0,
408
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
409
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
410
+ );
411
+ ```
412
+
413
+ ## AssertJ 断言模式
414
+
415
+ ```java
416
+ // 基础断言
417
+ assertThat(result).isNotNull();
418
+ assertThat(result.getCode()).isEqualTo(0);
419
+ assertThat(result.getData()).isInstanceOf(UserInfoVO.class);
420
+
421
+ // 集合断言
422
+ assertThat(userList)
423
+ .hasSize(3)
424
+ .extracting(UserInfoVO::getUsername)
425
+ .containsExactlyInAnyOrder("user1", "user2", "user3");
426
+
427
+ // 异常断言
428
+ assertThatThrownBy(() -> userService.getById(null))
429
+ .isInstanceOf(IllegalArgumentException.class)
430
+ .hasMessageContaining("ID不能为空");
431
+
432
+ // 时间断言
433
+ assertThat(user.getCreateTime())
434
+ .isNotNull()
435
+ .isBefore(LocalDateTime.now());
436
+
437
+ // JSON Path 断言(MockMvc)
438
+ mockMvc.perform(get("/api/users/{id}", userId))
439
+ .andExpect(jsonPath("$.code").value(0))
440
+ .andExpect(jsonPath("$.data.username").value("testuser"))
441
+ .andExpect(jsonPath("$.data.phone").value(matchesPattern("\\d{3}\\*{4}\\d{4}")))
442
+ .andExpect(jsonPath("$.data.roles[*].name").value(hasItem("ADMIN")));
443
+ ```
444
+
445
+ ## 最佳实践
446
+
447
+ 1. **使用 @Transactional** — 测试方法加 `@Transactional` 自动回滚,保持数据隔离
448
+ 2. **随机端口** — 使用 `RANDOM_PORT` 避免端口冲突
449
+ 3. **独立 Profile** — 使用 `@ActiveProfiles("test")` 隔离测试配置
450
+ 4. **MockBean 最小化** — 只 mock 真正需要隔离的外部依赖
451
+ 5. **数据准备方法** — 封装 `prepareXxx()` 方法统一管理测试数据
452
+ 6. **断言充分** — 不仅验证响应,还要验证数据库状态变化
453
+ 7. **打印调试** — 开发时用 `.andDo(print())`,提交前移除
454
+ 8. **测试分层** — 单元测试覆盖逻辑,集成测试覆盖链路
455
+
456
+ ## 常见问题排查
457
+
458
+ ### 测试上下文启动失败
459
+
460
+ ```
461
+ 原因: Bean 创建失败或配置缺失
462
+ 解决:
463
+ 1. 检查 application-test.yml 是否完整
464
+ 2. 检查 @MockBean 是否覆盖了必要的外部依赖
465
+ 3. 使用 @SpringBootTest(classes = XxxApplication.class) 指定启动类
466
+ ```
467
+
468
+ ### MockMvc 返回 404
469
+
470
+ ```
471
+ 原因: Controller 未被扫描到
472
+ 解决:
473
+ 1. 确认测试类和启动类在同一包路径下
474
+ 2. 使用 @SpringBootTest 而非 @WebMvcTest(后者只加载 Web 层)
475
+ 3. 检查 URL 路径是否正确(注意 context-path)
476
+ ```
477
+
478
+ ### 数据库相关测试失败
479
+
480
+ ```
481
+ 原因: H2 和 MySQL 语法差异
482
+ 解决:
483
+ 1. H2 使用 MODE=MySQL 兼容模式
484
+ 2. 使用 TestContainers + 真实 MySQL 获得更好的兼容性
485
+ 3. 检查 SQL 中是否有 H2 不支持的语法(如某些函数)
486
+ ```
487
+
488
+ ### @Transactional 回滚不生效
489
+
490
+ ```
491
+ 原因: 异步操作或多线程环境
492
+ 解决:
493
+ 1. 异步操作的数据变更不在事务管理范围内
494
+ 2. 使用 @AfterEach 手动清理数据
495
+ 3. 测试中避免使用异步方法
496
+ ```