ai-engineering-init 1.1.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/agents/code-reviewer.md +139 -0
- package/.claude/agents/project-manager.md +159 -0
- package/.claude/audio/completed.wav +0 -0
- package/.claude/commands/add-todo.md +255 -0
- package/.claude/commands/check.md +210 -0
- package/.claude/commands/crud.md +454 -0
- package/.claude/commands/dev.md +503 -0
- package/.claude/commands/init-docs.md +681 -0
- package/.claude/commands/next.md +251 -0
- package/.claude/commands/progress.md +242 -0
- package/.claude/commands/start.md +199 -0
- package/.claude/commands/sync.md +307 -0
- package/.claude/commands/update-status.md +428 -0
- package/.claude/docs/Mixin/344/275/277/347/224/250/346/214/207/345/215/227.md +299 -0
- package/.claude/docs/README.md +167 -0
- package/.claude/docs//345/211/215/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +599 -0
- package/.claude/docs//345/220/216/347/253/257/345/274/200/345/217/221/346/214/207/345/215/227.md +726 -0
- package/.claude/docs//345/267/245/344/275/234/346/265/201/345/274/200/345/217/221/346/214/207/345/215/227.md +714 -0
- package/.claude/docs//345/267/245/345/205/267/347/261/273/344/275/277/347/224/250/346/214/207/345/215/227.md +463 -0
- package/.claude/docs//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/350/247/204/350/214/203.md +390 -0
- package/.claude/docs//346/226/260/345/212/237/350/203/275/345/274/200/345/217/221/346/265/201/347/250/213/350/247/204/350/214/203.md +688 -0
- package/.claude/docs//346/226/260/351/241/271/347/233/256/345/274/200/345/217/221/346/265/201/347/250/213.md +365 -0
- package/.claude/docs//346/241/206/346/236/266/350/257/264/346/230/216.md +393 -0
- package/.claude/docs//350/267/257/347/224/261/351/205/215/347/275/256/346/214/207/345/215/227.md +246 -0
- package/.claude/framework-config.json +73 -0
- package/.claude/hooks/pre-tool-use.js +117 -0
- package/.claude/hooks/skill-forced-eval.js +167 -0
- package/.claude/hooks/stop.js +58 -0
- package/.claude/settings.json +41 -0
- package/.claude/skills/add-skill/SKILL.md +352 -0
- package/.claude/skills/api-development/SKILL.md +560 -0
- package/.claude/skills/architecture-design/SKILL.md +756 -0
- package/.claude/skills/backend-annotations/SKILL.md +674 -0
- package/.claude/skills/banana-image/CHANGELOG.md +37 -0
- package/.claude/skills/banana-image/README.md +146 -0
- package/.claude/skills/banana-image/SKILL.md +164 -0
- package/.claude/skills/banana-image/assets/logo.png +0 -0
- package/.claude/skills/banana-image/references/advanced-usage.md +189 -0
- package/.claude/skills/banana-image/scripts/apply_template.py +125 -0
- package/.claude/skills/banana-image/scripts/banana_image_exec.ts +412 -0
- package/.claude/skills/banana-image/scripts/batch_prep.py +82 -0
- package/.claude/skills/banana-image/scripts/package-lock.json +1437 -0
- package/.claude/skills/banana-image/scripts/package.json +18 -0
- package/.claude/skills/banana-image/scripts/requirements.txt +10 -0
- package/.claude/skills/banana-image/templates/poster.json +22 -0
- package/.claude/skills/banana-image/templates/product.json +17 -0
- package/.claude/skills/banana-image/templates/social.json +22 -0
- package/.claude/skills/banana-image/templates/thumbnail.json +17 -0
- package/.claude/skills/brainstorm/SKILL.md +648 -0
- package/.claude/skills/bug-detective/SKILL.md +1206 -0
- package/.claude/skills/code-patterns/SKILL.md +590 -0
- package/.claude/skills/collaborating-with-codex/SKILL.md +174 -0
- package/.claude/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
- package/.claude/skills/collaborating-with-gemini/SKILL.md +194 -0
- package/.claude/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
- package/.claude/skills/crud-development/SKILL.md +649 -0
- package/.claude/skills/data-permission/SKILL.md +599 -0
- package/.claude/skills/database-ops/SKILL.md +407 -0
- package/.claude/skills/error-handler/SKILL.md +371 -0
- package/.claude/skills/file-oss-management/SKILL.md +863 -0
- package/.claude/skills/git-workflow/SKILL.md +375 -0
- package/.claude/skills/json-serialization/SKILL.md +357 -0
- package/.claude/skills/leniu-api-development/SKILL.md +803 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +598 -0
- package/.claude/skills/leniu-backend-annotations/SKILL.md +664 -0
- package/.claude/skills/leniu-code-patterns/SKILL.md +365 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +1110 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +256 -0
- package/.claude/skills/leniu-database-ops/SKILL.md +426 -0
- package/.claude/skills/leniu-error-handler/SKILL.md +462 -0
- package/.claude/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/.claude/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.claude/skills/leniu-java-concurrent/SKILL.md +400 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +751 -0
- package/.claude/skills/leniu-java-export/SKILL.md +560 -0
- package/.claude/skills/leniu-java-logging/SKILL.md +832 -0
- package/.claude/skills/leniu-java-mq/SKILL.md +338 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +640 -0
- package/.claude/skills/leniu-java-report-query-param/SKILL.md +291 -0
- package/.claude/skills/leniu-java-task/SKILL.md +367 -0
- package/.claude/skills/leniu-java-total-line/SKILL.md +195 -0
- package/.claude/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
- package/.claude/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
- package/.claude/skills/leniu-mealtime/SKILL.md +215 -0
- package/.claude/skills/leniu-redis-cache/SKILL.md +316 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +520 -0
- package/.claude/skills/leniu-utils-toolkit/SKILL.md +380 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +114 -0
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
- package/.claude/skills/openspec-explore/SKILL.md +290 -0
- package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
- package/.claude/skills/openspec-new-change/SKILL.md +74 -0
- package/.claude/skills/openspec-onboard/SKILL.md +529 -0
- package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
- package/.claude/skills/performance-doctor/SKILL.md +627 -0
- package/.claude/skills/project-navigator/SKILL.md +305 -0
- package/.claude/skills/redis-cache/SKILL.md +839 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +633 -0
- package/.claude/skills/security-guard/SKILL.md +748 -0
- package/.claude/skills/sms-mail/SKILL.md +766 -0
- package/.claude/skills/social-login/SKILL.md +668 -0
- package/.claude/skills/store-pc/SKILL.md +366 -0
- package/.claude/skills/task-tracker/SKILL.md +307 -0
- package/.claude/skills/tech-decision/SKILL.md +393 -0
- package/.claude/skills/tenant-management/SKILL.md +603 -0
- package/.claude/skills/test-development/SKILL.md +755 -0
- package/.claude/skills/ui-pc/SKILL.md +438 -0
- package/.claude/skills/utils-toolkit/SKILL.md +615 -0
- package/.claude/skills/websocket-sse/SKILL.md +716 -0
- package/.claude/skills/workflow-engine/SKILL.md +676 -0
- package/.claude/templates//345/276/205/345/212/236/346/270/205/345/215/225/346/250/241/346/235/277.md +56 -0
- package/.claude/templates//351/234/200/346/261/202/346/226/207/346/241/243/346/250/241/346/235/277.md +85 -0
- package/.claude/templates//351/241/271/347/233/256/347/212/266/346/200/201/346/250/241/346/235/277.md +43 -0
- package/.codex/skills/add-skill/SKILL.md +352 -0
- package/.codex/skills/add-todo/SKILL.md +269 -0
- package/.codex/skills/api-development/SKILL.md +693 -0
- package/.codex/skills/architecture-design/SKILL.md +628 -0
- package/.codex/skills/backend-annotations/SKILL.md +664 -0
- package/.codex/skills/banana-image/CHANGELOG.md +37 -0
- package/.codex/skills/banana-image/README.md +146 -0
- package/.codex/skills/banana-image/SKILL.md +164 -0
- package/.codex/skills/banana-image/assets/logo.png +0 -0
- package/.codex/skills/banana-image/references/advanced-usage.md +189 -0
- package/.codex/skills/banana-image/scripts/apply_template.py +125 -0
- package/.codex/skills/banana-image/scripts/banana_image_exec.ts +412 -0
- package/.codex/skills/banana-image/scripts/batch_prep.py +82 -0
- package/.codex/skills/banana-image/scripts/package-lock.json +1437 -0
- package/.codex/skills/banana-image/scripts/package.json +18 -0
- package/.codex/skills/banana-image/scripts/requirements.txt +10 -0
- package/.codex/skills/banana-image/templates/poster.json +22 -0
- package/.codex/skills/banana-image/templates/product.json +17 -0
- package/.codex/skills/banana-image/templates/social.json +22 -0
- package/.codex/skills/banana-image/templates/thumbnail.json +17 -0
- package/.codex/skills/brainstorm/SKILL.md +648 -0
- package/.codex/skills/bug-detective/SKILL.md +1206 -0
- package/.codex/skills/check/SKILL.md +367 -0
- package/.codex/skills/code-patterns/SKILL.md +442 -0
- package/.codex/skills/collaborating-with-codex/SKILL.md +174 -0
- package/.codex/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
- package/.codex/skills/collaborating-with-gemini/SKILL.md +194 -0
- package/.codex/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
- package/.codex/skills/crud/SKILL.md +265 -0
- package/.codex/skills/crud-development/SKILL.md +637 -0
- package/.codex/skills/data-permission/SKILL.md +591 -0
- package/.codex/skills/database-ops/SKILL.md +553 -0
- package/.codex/skills/dev/SKILL.md +187 -0
- package/.codex/skills/error-handler/SKILL.md +361 -0
- package/.codex/skills/file-oss-management/SKILL.md +863 -0
- package/.codex/skills/git-workflow/SKILL.md +375 -0
- package/.codex/skills/init-docs/SKILL.md +194 -0
- package/.codex/skills/json-serialization/SKILL.md +357 -0
- package/.codex/skills/leniu-api-development/SKILL.md +803 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +594 -0
- package/.codex/skills/leniu-backend-annotations/SKILL.md +662 -0
- package/.codex/skills/leniu-code-patterns/SKILL.md +365 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +1110 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +256 -0
- package/.codex/skills/leniu-database-ops/SKILL.md +426 -0
- package/.codex/skills/leniu-error-handler/SKILL.md +462 -0
- package/.codex/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-concurrent/SKILL.md +400 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +751 -0
- package/.codex/skills/leniu-java-export/SKILL.md +560 -0
- package/.codex/skills/leniu-java-logging/SKILL.md +832 -0
- package/.codex/skills/leniu-java-mq/SKILL.md +338 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +640 -0
- package/.codex/skills/leniu-java-report-query-param/SKILL.md +291 -0
- package/.codex/skills/leniu-java-task/SKILL.md +367 -0
- package/.codex/skills/leniu-java-total-line/SKILL.md +195 -0
- package/.codex/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
- package/.codex/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
- package/.codex/skills/leniu-mealtime/SKILL.md +215 -0
- package/.codex/skills/leniu-redis-cache/SKILL.md +316 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +520 -0
- package/.codex/skills/leniu-utils-toolkit/SKILL.md +378 -0
- package/.codex/skills/next/SKILL.md +137 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +156 -0
- package/.codex/skills/openspec-archive-change/SKILL.md +114 -0
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.codex/skills/openspec-continue-change/SKILL.md +118 -0
- package/.codex/skills/openspec-explore/SKILL.md +290 -0
- package/.codex/skills/openspec-ff-change/SKILL.md +101 -0
- package/.codex/skills/openspec-new-change/SKILL.md +74 -0
- package/.codex/skills/openspec-onboard/SKILL.md +529 -0
- package/.codex/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.codex/skills/openspec-verify-change/SKILL.md +168 -0
- package/.codex/skills/performance-doctor/SKILL.md +627 -0
- package/.codex/skills/progress/SKILL.md +193 -0
- package/.codex/skills/project-navigator/SKILL.md +286 -0
- package/.codex/skills/redis-cache/SKILL.md +829 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +633 -0
- package/.codex/skills/security-guard/SKILL.md +739 -0
- package/.codex/skills/sms-mail/SKILL.md +766 -0
- package/.codex/skills/social-login/SKILL.md +668 -0
- package/.codex/skills/start/SKILL.md +154 -0
- package/.codex/skills/store-pc/SKILL.md +491 -0
- package/.codex/skills/sync/SKILL.md +149 -0
- package/.codex/skills/task-tracker/SKILL.md +307 -0
- package/.codex/skills/tech-decision/SKILL.md +393 -0
- package/.codex/skills/tenant-management/SKILL.md +603 -0
- package/.codex/skills/test-development/SKILL.md +755 -0
- package/.codex/skills/ui-pc/SKILL.md +475 -0
- package/.codex/skills/update-status/SKILL.md +159 -0
- package/.codex/skills/utils-toolkit/SKILL.md +593 -0
- package/.codex/skills/websocket-sse/SKILL.md +716 -0
- package/.codex/skills/workflow-engine/SKILL.md +676 -0
- package/.cursor/agents/code-reviewer.md +139 -0
- package/.cursor/agents/project-manager.md +159 -0
- package/.cursor/commands/opsx-apply.md +152 -0
- package/.cursor/commands/opsx-archive.md +157 -0
- package/.cursor/commands/opsx-bulk-archive.md +242 -0
- package/.cursor/commands/opsx-continue.md +114 -0
- package/.cursor/commands/opsx-explore.md +174 -0
- package/.cursor/commands/opsx-ff.md +94 -0
- package/.cursor/commands/opsx-new.md +69 -0
- package/.cursor/commands/opsx-onboard.md +525 -0
- package/.cursor/commands/opsx-sync.md +134 -0
- package/.cursor/commands/opsx-verify.md +164 -0
- package/.cursor/mcp.json +22 -0
- package/.cursor/skills/add-skill/SKILL.md +352 -0
- package/.cursor/skills/api-development/SKILL.md +560 -0
- package/.cursor/skills/architecture-design/SKILL.md +756 -0
- package/.cursor/skills/backend-annotations/SKILL.md +674 -0
- package/.cursor/skills/banana-image/CHANGELOG.md +37 -0
- package/.cursor/skills/banana-image/README.md +146 -0
- package/.cursor/skills/banana-image/SKILL.md +164 -0
- package/.cursor/skills/banana-image/assets/logo.png +0 -0
- package/.cursor/skills/banana-image/references/advanced-usage.md +189 -0
- package/.cursor/skills/banana-image/scripts/apply_template.py +125 -0
- package/.cursor/skills/banana-image/scripts/banana_image_exec.ts +412 -0
- package/.cursor/skills/banana-image/scripts/batch_prep.py +82 -0
- package/.cursor/skills/banana-image/scripts/package-lock.json +1437 -0
- package/.cursor/skills/banana-image/scripts/package.json +18 -0
- package/.cursor/skills/banana-image/scripts/requirements.txt +10 -0
- package/.cursor/skills/banana-image/templates/poster.json +22 -0
- package/.cursor/skills/banana-image/templates/product.json +17 -0
- package/.cursor/skills/banana-image/templates/social.json +22 -0
- package/.cursor/skills/banana-image/templates/thumbnail.json +17 -0
- package/.cursor/skills/brainstorm/SKILL.md +648 -0
- package/.cursor/skills/bug-detective/SKILL.md +1206 -0
- package/.cursor/skills/code-patterns/SKILL.md +590 -0
- package/.cursor/skills/collaborating-with-codex/SKILL.md +174 -0
- package/.cursor/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
- package/.cursor/skills/collaborating-with-gemini/SKILL.md +194 -0
- package/.cursor/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
- package/.cursor/skills/crud-development/SKILL.md +649 -0
- package/.cursor/skills/data-permission/SKILL.md +599 -0
- package/.cursor/skills/database-ops/SKILL.md +407 -0
- package/.cursor/skills/error-handler/SKILL.md +371 -0
- package/.cursor/skills/file-oss-management/SKILL.md +863 -0
- package/.cursor/skills/git-workflow/SKILL.md +375 -0
- package/.cursor/skills/json-serialization/SKILL.md +357 -0
- package/.cursor/skills/leniu-api-development/SKILL.md +803 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +598 -0
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +664 -0
- package/.cursor/skills/leniu-code-patterns/SKILL.md +365 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +1110 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +256 -0
- package/.cursor/skills/leniu-database-ops/SKILL.md +426 -0
- package/.cursor/skills/leniu-error-handler/SKILL.md +462 -0
- package/.cursor/skills/leniu-java-amount-handling/SKILL.md +461 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-concurrent/SKILL.md +400 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +751 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +560 -0
- package/.cursor/skills/leniu-java-logging/SKILL.md +832 -0
- package/.cursor/skills/leniu-java-mq/SKILL.md +338 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +640 -0
- package/.cursor/skills/leniu-java-report-query-param/SKILL.md +291 -0
- package/.cursor/skills/leniu-java-task/SKILL.md +367 -0
- package/.cursor/skills/leniu-java-total-line/SKILL.md +195 -0
- package/.cursor/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
- package/.cursor/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
- package/.cursor/skills/leniu-mealtime/SKILL.md +215 -0
- package/.cursor/skills/leniu-redis-cache/SKILL.md +316 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +520 -0
- package/.cursor/skills/leniu-utils-toolkit/SKILL.md +380 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +156 -0
- package/.cursor/skills/openspec-archive-change/SKILL.md +114 -0
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.cursor/skills/openspec-continue-change/SKILL.md +118 -0
- package/.cursor/skills/openspec-explore/SKILL.md +290 -0
- package/.cursor/skills/openspec-ff-change/SKILL.md +101 -0
- package/.cursor/skills/openspec-new-change/SKILL.md +74 -0
- package/.cursor/skills/openspec-onboard/SKILL.md +529 -0
- package/.cursor/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.cursor/skills/openspec-verify-change/SKILL.md +168 -0
- package/.cursor/skills/performance-doctor/SKILL.md +627 -0
- package/.cursor/skills/project-navigator/SKILL.md +305 -0
- package/.cursor/skills/redis-cache/SKILL.md +839 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +633 -0
- package/.cursor/skills/security-guard/SKILL.md +748 -0
- package/.cursor/skills/sms-mail/SKILL.md +766 -0
- package/.cursor/skills/social-login/SKILL.md +668 -0
- package/.cursor/skills/store-pc/SKILL.md +366 -0
- package/.cursor/skills/task-tracker/SKILL.md +307 -0
- package/.cursor/skills/tech-decision/SKILL.md +393 -0
- package/.cursor/skills/tenant-management/SKILL.md +603 -0
- package/.cursor/skills/test-development/SKILL.md +755 -0
- package/.cursor/skills/ui-pc/SKILL.md +438 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +615 -0
- package/.cursor/skills/websocket-sse/SKILL.md +716 -0
- package/.cursor/skills/workflow-engine/SKILL.md +676 -0
- package/AGENTS.md +669 -0
- package/CLAUDE.md +205 -0
- package/README.md +205 -0
- package/bin/index.js +179 -0
- package/init.sh +178 -0
- package/package.json +27 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-development
|
|
3
|
+
description: |
|
|
4
|
+
测试开发技能,编写单元测试、集成测试、Controller测试。基于 JUnit5 + Spring Boot Test + Mockito 标准测试框架。
|
|
5
|
+
|
|
6
|
+
触发场景:
|
|
7
|
+
- 编写单元测试(工具类、枚举、POJO)
|
|
8
|
+
- 编写 Spring 集成测试(Service、Controller、Mapper)
|
|
9
|
+
- Mock 外部依赖
|
|
10
|
+
- 参数化测试
|
|
11
|
+
- 测试数据构造
|
|
12
|
+
- 测试覆盖率提升
|
|
13
|
+
|
|
14
|
+
触发词:测试、单元测试、集成测试、@Test、JUnit5、JUnit、Mockito、Mock、断言、test、测试用例、测试覆盖率、测试数据、@SpringBootTest、@Mock、@MockBean、Assertions、测试类、测试方法、@ParameterizedTest、参数化测试、@BeforeEach、@AfterEach、@DisplayName
|
|
15
|
+
|
|
16
|
+
注意:本项目使用标准的 JUnit5 + Spring Boot Test,没有自定义测试基类。
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# 测试开发规范
|
|
20
|
+
|
|
21
|
+
> **核心原则**:本项目使用标准的 JUnit5 + Spring Boot Test,根据测试场景选择是否启动 Spring 容器!
|
|
22
|
+
|
|
23
|
+
## 测试分层策略
|
|
24
|
+
|
|
25
|
+
| 层次 | 测试类型 | 是否启动 Spring | 特点 | 执行速度 |
|
|
26
|
+
|------|---------|----------------|------|---------|
|
|
27
|
+
| **单元测试** | 工具类/枚举/POJO | ❌ 否 | 纯 JUnit5,无依赖注入 | < 1s |
|
|
28
|
+
| **集成测试** | Service/Controller/Mapper | ✅ 是 | 使用 `@SpringBootTest`,完整 Spring 容器 | 5-10s |
|
|
29
|
+
|
|
30
|
+
## 测试文件位置
|
|
31
|
+
|
|
32
|
+
| 类型 | 位置 | 示例 |
|
|
33
|
+
|------|------|------|
|
|
34
|
+
| 测试类 | `src/test/java` | `ruoyi-admin/src/test/java/org/dromara/test/` |
|
|
35
|
+
| 测试资源 | `src/test/resources` | 测试配置文件、测试数据等 |
|
|
36
|
+
|
|
37
|
+
## 核心依赖
|
|
38
|
+
|
|
39
|
+
Spring Boot Test 已包含在 `spring-boot-starter-test` 中,包含:
|
|
40
|
+
- **JUnit 5**:测试框架
|
|
41
|
+
- **Mockito**:Mock 框架
|
|
42
|
+
- **Spring Test**:Spring 测试支持
|
|
43
|
+
|
|
44
|
+
```xml
|
|
45
|
+
<!-- 在业务模块的 pom.xml 中已包含 -->
|
|
46
|
+
<dependency>
|
|
47
|
+
<groupId>org.springframework.boot</groupId>
|
|
48
|
+
<artifactId>spring-boot-starter-test</artifactId>
|
|
49
|
+
<scope>test</scope>
|
|
50
|
+
</dependency>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. 单元测试(纯 JUnit5)
|
|
56
|
+
|
|
57
|
+
**适用场景:** 工具类、枚举类、POJO、算法逻辑(无需 Spring 容器)
|
|
58
|
+
|
|
59
|
+
**特点:** 不使用 `@SpringBootTest`,执行速度快,适合纯逻辑测试。
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
package org.dromara.test;
|
|
63
|
+
|
|
64
|
+
import org.junit.jupiter.api.Assertions;
|
|
65
|
+
import org.junit.jupiter.api.DisplayName;
|
|
66
|
+
import org.junit.jupiter.api.Test;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 断言单元测试案例
|
|
70
|
+
* 参考:ruoyi-admin/src/test/java/org/dromara/test/AssertUnitTest.java
|
|
71
|
+
*/
|
|
72
|
+
@DisplayName("断言单元测试案例")
|
|
73
|
+
public class AssertUnitTest {
|
|
74
|
+
|
|
75
|
+
@Test
|
|
76
|
+
@DisplayName("测试 assertEquals 方法")
|
|
77
|
+
public void testAssertEquals() {
|
|
78
|
+
Assertions.assertEquals("666", new String("666"));
|
|
79
|
+
Assertions.assertNotEquals("777", new String("666"));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Test
|
|
83
|
+
@DisplayName("测试 assertTrue 方法")
|
|
84
|
+
public void testAssertTrue() {
|
|
85
|
+
Assertions.assertTrue(true);
|
|
86
|
+
Assertions.assertFalse(false);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test
|
|
90
|
+
@DisplayName("测试 assertNull 方法")
|
|
91
|
+
public void testAssertNull() {
|
|
92
|
+
Assertions.assertNull(null);
|
|
93
|
+
Assertions.assertNotNull("not null");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**常用断言方法:**
|
|
99
|
+
|
|
100
|
+
```java
|
|
101
|
+
// 相等性断言
|
|
102
|
+
Assertions.assertEquals(expected, actual);
|
|
103
|
+
Assertions.assertNotEquals(expected, actual);
|
|
104
|
+
|
|
105
|
+
// 同一性断言
|
|
106
|
+
Assertions.assertSame(obj1, obj2);
|
|
107
|
+
Assertions.assertNotSame(obj1, obj2);
|
|
108
|
+
|
|
109
|
+
// 布尔断言
|
|
110
|
+
Assertions.assertTrue(condition);
|
|
111
|
+
Assertions.assertFalse(condition);
|
|
112
|
+
|
|
113
|
+
// 空值断言
|
|
114
|
+
Assertions.assertNull(object);
|
|
115
|
+
Assertions.assertNotNull(object);
|
|
116
|
+
|
|
117
|
+
// 异常断言
|
|
118
|
+
Assertions.assertThrows(Exception.class, () -> {
|
|
119
|
+
// 会抛出异常的代码
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 2. Spring 集成测试(@SpringBootTest)
|
|
126
|
+
|
|
127
|
+
**适用场景:** 需要 Spring 容器的测试(Service、Controller、Mapper、需要依赖注入的场景)
|
|
128
|
+
|
|
129
|
+
**特点:** 使用 `@SpringBootTest` 启动完整的 Spring 容器,可以使用 `@Autowired` 注入 Bean。
|
|
130
|
+
|
|
131
|
+
**重要提示:** `@SpringBootTest` 只能在 SpringBoot 主包下使用,需包含 main 方法与 yml 配置文件。
|
|
132
|
+
|
|
133
|
+
```java
|
|
134
|
+
package org.dromara.test;
|
|
135
|
+
|
|
136
|
+
import org.dromara.common.web.config.properties.CaptchaProperties;
|
|
137
|
+
import org.junit.jupiter.api.*;
|
|
138
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
139
|
+
import org.springframework.boot.test.context.SpringBootTest;
|
|
140
|
+
|
|
141
|
+
import java.util.concurrent.TimeUnit;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Spring 集成测试案例
|
|
145
|
+
* 参考:ruoyi-admin/src/test/java/org/dromara/test/DemoUnitTest.java
|
|
146
|
+
*/
|
|
147
|
+
@SpringBootTest
|
|
148
|
+
@DisplayName("单元测试案例")
|
|
149
|
+
public class DemoUnitTest {
|
|
150
|
+
|
|
151
|
+
@Autowired
|
|
152
|
+
private CaptchaProperties captchaProperties;
|
|
153
|
+
|
|
154
|
+
@Test
|
|
155
|
+
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
|
|
156
|
+
public void testTest() {
|
|
157
|
+
System.out.println(captchaProperties);
|
|
158
|
+
Assertions.assertNotNull(captchaProperties);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Test
|
|
162
|
+
@DisplayName("测试 @Disabled 注解")
|
|
163
|
+
@Disabled // 禁用此测试
|
|
164
|
+
public void testDisabled() {
|
|
165
|
+
System.out.println("此测试被禁用");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@Test
|
|
169
|
+
@DisplayName("测试 @Timeout 注解")
|
|
170
|
+
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
|
|
171
|
+
public void testTimeout() throws InterruptedException {
|
|
172
|
+
Thread.sleep(1000); // 1秒,不会超时
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@RepeatedTest(3)
|
|
176
|
+
@DisplayName("测试 @RepeatedTest 注解")
|
|
177
|
+
public void testRepeatedTest() {
|
|
178
|
+
System.out.println("重复测试");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@BeforeAll
|
|
182
|
+
public static void testBeforeAll() {
|
|
183
|
+
System.out.println("@BeforeAll - 所有测试前执行一次");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@BeforeEach
|
|
187
|
+
public void testBeforeEach() {
|
|
188
|
+
System.out.println("@BeforeEach - 每个测试前执行");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@AfterEach
|
|
192
|
+
public void testAfterEach() {
|
|
193
|
+
System.out.println("@AfterEach - 每个测试后执行");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@AfterAll
|
|
197
|
+
public static void testAfterAll() {
|
|
198
|
+
System.out.println("@AfterAll - 所有测试后执行一次");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 3. Service 测试(@SpringBootTest + @Transactional)
|
|
206
|
+
|
|
207
|
+
**适用场景:** Service 层业务逻辑测试,需要数据库操作
|
|
208
|
+
|
|
209
|
+
**特点:** 使用 `@SpringBootTest` + `@Transactional`,测试后自动回滚,不污染数据库。
|
|
210
|
+
|
|
211
|
+
```java
|
|
212
|
+
package org.dromara.demo.service;
|
|
213
|
+
|
|
214
|
+
import org.dromara.demo.domain.bo.TestDemoBo;
|
|
215
|
+
import org.dromara.demo.domain.vo.TestDemoVo;
|
|
216
|
+
import org.junit.jupiter.api.*;
|
|
217
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
218
|
+
import org.springframework.boot.test.context.SpringBootTest;
|
|
219
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Service 层测试示例
|
|
223
|
+
*/
|
|
224
|
+
@SpringBootTest
|
|
225
|
+
@Transactional // 测试后自动回滚
|
|
226
|
+
@DisplayName("TestDemo Service 测试")
|
|
227
|
+
public class TestDemoServiceTest {
|
|
228
|
+
|
|
229
|
+
@Autowired
|
|
230
|
+
private ITestDemoService testDemoService;
|
|
231
|
+
|
|
232
|
+
@Test
|
|
233
|
+
@DisplayName("测试添加数据")
|
|
234
|
+
public void testAdd() {
|
|
235
|
+
// Arrange - 准备数据
|
|
236
|
+
TestDemoBo bo = new TestDemoBo();
|
|
237
|
+
bo.setDeptId(103L);
|
|
238
|
+
bo.setUserId(1L);
|
|
239
|
+
bo.setOrderNum(1);
|
|
240
|
+
bo.setTestKey("测试数据");
|
|
241
|
+
bo.setValue("test_value");
|
|
242
|
+
|
|
243
|
+
// Act - 执行操作
|
|
244
|
+
Boolean result = testDemoService.insertByBo(bo);
|
|
245
|
+
|
|
246
|
+
// Assert - 验证结果
|
|
247
|
+
Assertions.assertTrue(result);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@Test
|
|
251
|
+
@DisplayName("测试查询详情")
|
|
252
|
+
public void testGetById() {
|
|
253
|
+
// 先添加测试数据
|
|
254
|
+
TestDemoBo bo = new TestDemoBo();
|
|
255
|
+
bo.setDeptId(103L);
|
|
256
|
+
bo.setUserId(1L);
|
|
257
|
+
bo.setOrderNum(2);
|
|
258
|
+
bo.setTestKey("测试查询");
|
|
259
|
+
bo.setValue("test_query");
|
|
260
|
+
testDemoService.insertByBo(bo);
|
|
261
|
+
|
|
262
|
+
// 查询详情
|
|
263
|
+
TestDemoVo vo = testDemoService.queryById(bo.getId());
|
|
264
|
+
|
|
265
|
+
// 断言
|
|
266
|
+
Assertions.assertNotNull(vo);
|
|
267
|
+
Assertions.assertEquals("测试查询", vo.getTestKey());
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@Test
|
|
271
|
+
@DisplayName("测试更新数据")
|
|
272
|
+
public void testUpdate() {
|
|
273
|
+
// 先添加测试数据
|
|
274
|
+
TestDemoBo bo = new TestDemoBo();
|
|
275
|
+
bo.setDeptId(103L);
|
|
276
|
+
bo.setUserId(1L);
|
|
277
|
+
bo.setOrderNum(3);
|
|
278
|
+
bo.setTestKey("原始名称");
|
|
279
|
+
bo.setValue("original");
|
|
280
|
+
testDemoService.insertByBo(bo);
|
|
281
|
+
|
|
282
|
+
// 更新数据
|
|
283
|
+
bo.setTestKey("更新后名称");
|
|
284
|
+
Boolean result = testDemoService.updateByBo(bo);
|
|
285
|
+
|
|
286
|
+
// 验证
|
|
287
|
+
Assertions.assertTrue(result);
|
|
288
|
+
TestDemoVo vo = testDemoService.queryById(bo.getId());
|
|
289
|
+
Assertions.assertEquals("更新后名称", vo.getTestKey());
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Mock 外部依赖示例:**
|
|
295
|
+
|
|
296
|
+
```java
|
|
297
|
+
@SpringBootTest
|
|
298
|
+
@DisplayName("订单服务测试")
|
|
299
|
+
public class OrderServiceTest {
|
|
300
|
+
|
|
301
|
+
@Autowired
|
|
302
|
+
private IOrderService orderService;
|
|
303
|
+
|
|
304
|
+
@MockBean // Mock 支付服务
|
|
305
|
+
private IPaymentService paymentService;
|
|
306
|
+
|
|
307
|
+
@Test
|
|
308
|
+
@DisplayName("测试订单支付(Mock 支付服务)")
|
|
309
|
+
public void testPayOrder() {
|
|
310
|
+
Long orderId = 123L;
|
|
311
|
+
|
|
312
|
+
// Mock 行为
|
|
313
|
+
Mockito.when(paymentService.pay(orderId)).thenReturn(true);
|
|
314
|
+
|
|
315
|
+
// 执行
|
|
316
|
+
Boolean success = orderService.payOrder(orderId);
|
|
317
|
+
|
|
318
|
+
// 断言
|
|
319
|
+
Assertions.assertTrue(success);
|
|
320
|
+
Mockito.verify(paymentService, Mockito.times(1)).pay(orderId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 4. Controller 测试(@SpringBootTest + MockMvc)
|
|
328
|
+
|
|
329
|
+
**适用场景:** HTTP 接口测试,完整请求链路
|
|
330
|
+
|
|
331
|
+
**特点:** 使用 `@SpringBootTest` + `@AutoConfigureMockMvc`,模拟 HTTP 请求。
|
|
332
|
+
|
|
333
|
+
```java
|
|
334
|
+
package org.dromara.demo.controller;
|
|
335
|
+
|
|
336
|
+
import org.junit.jupiter.api.*;
|
|
337
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
338
|
+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
|
339
|
+
import org.springframework.boot.test.context.SpringBootTest;
|
|
340
|
+
import org.springframework.http.MediaType;
|
|
341
|
+
import org.springframework.test.web.servlet.MockMvc;
|
|
342
|
+
|
|
343
|
+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
|
344
|
+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Controller 层测试示例
|
|
348
|
+
*/
|
|
349
|
+
@SpringBootTest
|
|
350
|
+
@AutoConfigureMockMvc
|
|
351
|
+
@DisplayName("TestDemo Controller 测试")
|
|
352
|
+
public class TestDemoControllerTest {
|
|
353
|
+
|
|
354
|
+
@Autowired
|
|
355
|
+
private MockMvc mockMvc;
|
|
356
|
+
|
|
357
|
+
@Test
|
|
358
|
+
@DisplayName("测试分页查询")
|
|
359
|
+
public void testPageList() throws Exception {
|
|
360
|
+
mockMvc.perform(get("/demo/demo/list")
|
|
361
|
+
.param("pageNum", "1")
|
|
362
|
+
.param("pageSize", "10"))
|
|
363
|
+
.andExpect(status().isOk())
|
|
364
|
+
.andExpect(jsonPath("$.code").value(200));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
@Test
|
|
368
|
+
@DisplayName("测试查询详情")
|
|
369
|
+
public void testGetById() throws Exception {
|
|
370
|
+
mockMvc.perform(get("/demo/demo/1"))
|
|
371
|
+
.andExpect(status().isOk())
|
|
372
|
+
.andExpect(jsonPath("$.code").value(200));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
@Test
|
|
376
|
+
@DisplayName("测试添加数据")
|
|
377
|
+
public void testAdd() throws Exception {
|
|
378
|
+
String requestBody = """
|
|
379
|
+
{
|
|
380
|
+
"deptId": 103,
|
|
381
|
+
"userId": 1,
|
|
382
|
+
"orderNum": 1,
|
|
383
|
+
"testKey": "测试数据",
|
|
384
|
+
"value": "test_value"
|
|
385
|
+
}
|
|
386
|
+
""";
|
|
387
|
+
|
|
388
|
+
mockMvc.perform(post("/demo/demo")
|
|
389
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
390
|
+
.content(requestBody))
|
|
391
|
+
.andExpect(status().isOk())
|
|
392
|
+
.andExpect(jsonPath("$.code").value(200));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@Test
|
|
396
|
+
@DisplayName("测试更新数据")
|
|
397
|
+
public void testUpdate() throws Exception {
|
|
398
|
+
String requestBody = """
|
|
399
|
+
{
|
|
400
|
+
"id": 1,
|
|
401
|
+
"deptId": 103,
|
|
402
|
+
"userId": 1,
|
|
403
|
+
"orderNum": 1,
|
|
404
|
+
"testKey": "更新后数据",
|
|
405
|
+
"value": "updated_value"
|
|
406
|
+
}
|
|
407
|
+
""";
|
|
408
|
+
|
|
409
|
+
mockMvc.perform(put("/demo/demo")
|
|
410
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
411
|
+
.content(requestBody))
|
|
412
|
+
.andExpect(status().isOk())
|
|
413
|
+
.andExpect(jsonPath("$.code").value(200));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@Test
|
|
417
|
+
@DisplayName("测试删除数据")
|
|
418
|
+
public void testDelete() throws Exception {
|
|
419
|
+
mockMvc.perform(delete("/demo/demo/1,2,3"))
|
|
420
|
+
.andExpect(status().isOk())
|
|
421
|
+
.andExpect(jsonPath("$.code").value(200));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 5. 参数化测试
|
|
429
|
+
|
|
430
|
+
**适用场景:** 需要用多组数据测试同一个方法
|
|
431
|
+
|
|
432
|
+
**特点:** 使用 `@ParameterizedTest` 替代 `@Test`,配合数据源注解提供测试数据。
|
|
433
|
+
|
|
434
|
+
```java
|
|
435
|
+
package org.dromara.test;
|
|
436
|
+
|
|
437
|
+
import org.dromara.common.core.enums.UserType;
|
|
438
|
+
import org.junit.jupiter.api.*;
|
|
439
|
+
import org.junit.jupiter.params.ParameterizedTest;
|
|
440
|
+
import org.junit.jupiter.params.provider.*;
|
|
441
|
+
|
|
442
|
+
import java.util.ArrayList;
|
|
443
|
+
import java.util.List;
|
|
444
|
+
import java.util.stream.Stream;
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* 参数化测试案例
|
|
448
|
+
* 参考:ruoyi-admin/src/test/java/org/dromara/test/ParamUnitTest.java
|
|
449
|
+
*/
|
|
450
|
+
@DisplayName("带参数单元测试案例")
|
|
451
|
+
public class ParamUnitTest {
|
|
452
|
+
|
|
453
|
+
@ParameterizedTest
|
|
454
|
+
@DisplayName("测试 @ValueSource 注解")
|
|
455
|
+
@ValueSource(strings = {"t1", "t2", "t3"})
|
|
456
|
+
public void testValueSource(String str) {
|
|
457
|
+
System.out.println(str);
|
|
458
|
+
Assertions.assertNotNull(str);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@ParameterizedTest
|
|
462
|
+
@DisplayName("测试 @NullSource 注解")
|
|
463
|
+
@NullSource
|
|
464
|
+
public void testNullSource(String str) {
|
|
465
|
+
System.out.println(str);
|
|
466
|
+
Assertions.assertNull(str);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
@ParameterizedTest
|
|
470
|
+
@DisplayName("测试 @EnumSource 注解")
|
|
471
|
+
@EnumSource(UserType.class)
|
|
472
|
+
public void testEnumSource(UserType type) {
|
|
473
|
+
System.out.println(type.getUserType());
|
|
474
|
+
Assertions.assertNotNull(type);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@ParameterizedTest
|
|
478
|
+
@DisplayName("测试 @MethodSource 注解")
|
|
479
|
+
@MethodSource("getParam")
|
|
480
|
+
public void testMethodSource(String str) {
|
|
481
|
+
System.out.println(str);
|
|
482
|
+
Assertions.assertNotNull(str);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
public static Stream<String> getParam() {
|
|
486
|
+
List<String> list = new ArrayList<>();
|
|
487
|
+
list.add("t1");
|
|
488
|
+
list.add("t2");
|
|
489
|
+
list.add("t3");
|
|
490
|
+
return list.stream();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
@BeforeEach
|
|
494
|
+
public void testBeforeEach() {
|
|
495
|
+
System.out.println("@BeforeEach - 每个测试前执行");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@AfterEach
|
|
499
|
+
public void testAfterEach() {
|
|
500
|
+
System.out.println("@AfterEach - 每个测试后执行");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**更多参数化测试示例:**
|
|
506
|
+
|
|
507
|
+
```java
|
|
508
|
+
// CSV 数据源
|
|
509
|
+
@ParameterizedTest
|
|
510
|
+
@DisplayName("测试 CSV 数据")
|
|
511
|
+
@CsvSource({
|
|
512
|
+
"1, Banner, 横幅广告",
|
|
513
|
+
"2, Popup, 弹窗广告"
|
|
514
|
+
})
|
|
515
|
+
public void testCsvData(String code, String name, String desc) {
|
|
516
|
+
Assertions.assertNotNull(code);
|
|
517
|
+
Assertions.assertNotNull(name);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// 多个值源
|
|
521
|
+
@ParameterizedTest
|
|
522
|
+
@DisplayName("测试多个整数")
|
|
523
|
+
@ValueSource(ints = {1, 2, 3, 4, 5})
|
|
524
|
+
public void testIntValues(int num) {
|
|
525
|
+
Assertions.assertTrue(num > 0);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 空值和空字符串
|
|
529
|
+
@ParameterizedTest
|
|
530
|
+
@DisplayName("测试空值")
|
|
531
|
+
@NullAndEmptySource
|
|
532
|
+
@ValueSource(strings = {" ", "\t", "\n"})
|
|
533
|
+
public void testBlankStrings(String input) {
|
|
534
|
+
Assertions.assertTrue(input == null || input.isBlank());
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## 6. 测试标签和分组(@Tag)
|
|
541
|
+
|
|
542
|
+
**适用场景:** 需要对测试进行分组,选择性运行某些测试
|
|
543
|
+
|
|
544
|
+
**特点:** 使用 `@Tag` 注解标记测试,可以按标签过滤执行。
|
|
545
|
+
|
|
546
|
+
```java
|
|
547
|
+
package org.dromara.test;
|
|
548
|
+
|
|
549
|
+
import org.junit.jupiter.api.*;
|
|
550
|
+
import org.springframework.boot.test.context.SpringBootTest;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* 标签单元测试案例
|
|
554
|
+
* 参考:ruoyi-admin/src/test/java/org/dromara/test/TagUnitTest.java
|
|
555
|
+
*/
|
|
556
|
+
@SpringBootTest
|
|
557
|
+
@DisplayName("标签单元测试案例")
|
|
558
|
+
public class TagUnitTest {
|
|
559
|
+
|
|
560
|
+
@Test
|
|
561
|
+
@Tag("dev")
|
|
562
|
+
@DisplayName("测试 @Tag dev")
|
|
563
|
+
public void testTagDev() {
|
|
564
|
+
System.out.println("dev 环境测试");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
@Test
|
|
568
|
+
@Tag("prod")
|
|
569
|
+
@DisplayName("测试 @Tag prod")
|
|
570
|
+
public void testTagProd() {
|
|
571
|
+
System.out.println("prod 环境测试");
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
@Test
|
|
575
|
+
@Tag("local")
|
|
576
|
+
@DisplayName("测试 @Tag local")
|
|
577
|
+
public void testTagLocal() {
|
|
578
|
+
System.out.println("local 环境测试");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@Test
|
|
582
|
+
@Tag("exclude")
|
|
583
|
+
@DisplayName("测试 @Tag exclude")
|
|
584
|
+
public void testTagExclude() {
|
|
585
|
+
System.out.println("排除的测试");
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
@BeforeEach
|
|
589
|
+
public void testBeforeEach() {
|
|
590
|
+
System.out.println("@BeforeEach - 每个测试前执行");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
@AfterEach
|
|
594
|
+
public void testAfterEach() {
|
|
595
|
+
System.out.println("@AfterEach - 每个测试后执行");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
**运行指定标签的测试:**
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Maven 运行指定标签的测试
|
|
604
|
+
mvn test -Dgroups=dev
|
|
605
|
+
|
|
606
|
+
# Maven 排除指定标签的测试
|
|
607
|
+
mvn test -DexcludedGroups=exclude
|
|
608
|
+
|
|
609
|
+
# IDEA 中配置标签过滤
|
|
610
|
+
# Run → Edit Configurations → Test kind: Tags → Tag expression: dev
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## 开发检查清单
|
|
616
|
+
|
|
617
|
+
### 测试文件规范
|
|
618
|
+
|
|
619
|
+
- [ ] **测试类命名**:`{被测试类名}Test`(如 `StringUtilsTest`、`DemoUnitTest`)
|
|
620
|
+
- [ ] **测试类位置**:`src/test/java` 目录下,包路径与源码一致
|
|
621
|
+
- [ ] **测试方法命名**:`test{功能}`(如 `testIsBlank`、`testAdd`)
|
|
622
|
+
- [ ] **使用 @DisplayName**:为测试类和方法添加中文描述
|
|
623
|
+
|
|
624
|
+
### 注解选择
|
|
625
|
+
|
|
626
|
+
- [ ] **纯单元测试**:不使用 `@SpringBootTest`,适合工具类/枚举/POJO
|
|
627
|
+
- [ ] **集成测试**:使用 `@SpringBootTest`,适合 Service/Controller/Mapper
|
|
628
|
+
- [ ] **Service 测试**:使用 `@SpringBootTest` + `@Transactional`,测试后自动回滚
|
|
629
|
+
- [ ] **Controller 测试**:使用 `@SpringBootTest` + `@AutoConfigureMockMvc`
|
|
630
|
+
|
|
631
|
+
### 断言规范
|
|
632
|
+
|
|
633
|
+
- [ ] **使用 JUnit5 Assertions**:`Assertions.assertEquals()` / `Assertions.assertTrue()` 等
|
|
634
|
+
- [ ] **异常断言**:`Assertions.assertThrows(Exception.class, () -> {...})`
|
|
635
|
+
- [ ] **空值断言**:`Assertions.assertNull()` / `Assertions.assertNotNull()`
|
|
636
|
+
|
|
637
|
+
### Mock 规范
|
|
638
|
+
|
|
639
|
+
- [ ] **Spring 测试**:使用 `@MockBean` Mock Spring Bean
|
|
640
|
+
- [ ] **单元测试**:使用 `@Mock` / `@InjectMocks`(Mockito)
|
|
641
|
+
- [ ] **验证调用**:使用 `Mockito.verify()` 验证 Mock 方法被调用
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## 常见错误
|
|
646
|
+
|
|
647
|
+
| 错误写法 | 正确写法 | 原因 |
|
|
648
|
+
|---------|---------|------|
|
|
649
|
+
| 测试类在 `src/main/java` | 测试类在 `src/test/java` | 测试代码不应打包到生产环境 |
|
|
650
|
+
| `@Test public void test()` | `@Test @DisplayName("xxx") public void testXxx()` | 缺少描述和命名规范 |
|
|
651
|
+
| 忘记添加 `@SpringBootTest` | 需要依赖注入时添加 `@SpringBootTest` | 无法注入 Spring Bean |
|
|
652
|
+
| `@SpringBootTest` 在非主包下 | 测试类必须在主包下(包含 main 方法) | `@SpringBootTest` 需要找到主类 |
|
|
653
|
+
| Mock 后不验证调用 | `Mockito.verify(mockObj).method()` | Mock 应验证调用次数和参数 |
|
|
654
|
+
| 测试方法相互依赖 | 每个测试方法独立 | 测试应独立可并行执行 |
|
|
655
|
+
| Service 测试不加 `@Transactional` | 使用 `@Transactional` 自动回滚 | 避免污染数据库 |
|
|
656
|
+
| 硬编码测试数据 | 使用变量或常量 | 提高可维护性 |
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## 异常测试
|
|
661
|
+
|
|
662
|
+
**适用场景:** 测试方法是否正确抛出异常
|
|
663
|
+
|
|
664
|
+
```java
|
|
665
|
+
import org.dromara.common.core.exception.ServiceException;
|
|
666
|
+
import org.junit.jupiter.api.Assertions;
|
|
667
|
+
import org.junit.jupiter.api.DisplayName;
|
|
668
|
+
import org.junit.jupiter.api.Test;
|
|
669
|
+
|
|
670
|
+
@Test
|
|
671
|
+
@DisplayName("测试添加数据 - key键为空抛出异常")
|
|
672
|
+
public void testAdd_ThrowsException() {
|
|
673
|
+
TestDemoBo bo = new TestDemoBo();
|
|
674
|
+
// 不设置 testKey(必填字段)
|
|
675
|
+
|
|
676
|
+
// 断言抛出 ServiceException 异常
|
|
677
|
+
ServiceException exception = Assertions.assertThrows(
|
|
678
|
+
ServiceException.class,
|
|
679
|
+
() -> testDemoService.insertByBo(bo)
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
// 验证异常消息
|
|
683
|
+
Assertions.assertTrue(exception.getMessage().contains("key键不能为空"));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
@Test
|
|
687
|
+
@DisplayName("测试空指针异常")
|
|
688
|
+
public void testNullPointerException() {
|
|
689
|
+
Assertions.assertThrows(
|
|
690
|
+
NullPointerException.class,
|
|
691
|
+
() -> {
|
|
692
|
+
String str = null;
|
|
693
|
+
str.length();
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## 运行测试
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
# Maven 运行所有测试
|
|
705
|
+
mvn test
|
|
706
|
+
|
|
707
|
+
# Maven 运行单个测试类
|
|
708
|
+
mvn test -Dtest=AdServiceTest
|
|
709
|
+
|
|
710
|
+
# Maven 运行单个测试方法
|
|
711
|
+
mvn test -Dtest=AdServiceTest#testAddAd
|
|
712
|
+
|
|
713
|
+
# IDEA 中运行
|
|
714
|
+
# 右键测试类/方法 → Run 'XXTest'
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## FAQ
|
|
720
|
+
|
|
721
|
+
### Q1: 什么时候使用 @SpringBootTest?
|
|
722
|
+
|
|
723
|
+
**A:**
|
|
724
|
+
- **不使用 @SpringBootTest**:工具类、枚举、POJO(纯逻辑测试,不需要 Spring 容器)
|
|
725
|
+
- **使用 @SpringBootTest**:Service、Controller、Mapper(需要依赖注入和 Spring 容器)
|
|
726
|
+
|
|
727
|
+
### Q2: Service 测试会污染数据库吗?
|
|
728
|
+
|
|
729
|
+
**A:** 不会。使用 `@SpringBootTest` + `@Transactional`,测试结束后自动回滚,不会污染数据库。
|
|
730
|
+
|
|
731
|
+
### Q3: 如何测试私有方法?
|
|
732
|
+
|
|
733
|
+
**A:**
|
|
734
|
+
- 不要直接测试私有方法
|
|
735
|
+
- 通过公共方法间接测试
|
|
736
|
+
- 如果私有方法太复杂,考虑提取为独立类
|
|
737
|
+
|
|
738
|
+
### Q4: Mock 和真实测试如何选择?
|
|
739
|
+
|
|
740
|
+
**A:**
|
|
741
|
+
- **单元测试**: 优先 Mock 外部依赖(快速、隔离)
|
|
742
|
+
- **集成测试**: 使用真实依赖(准确、完整)
|
|
743
|
+
- **平衡**: 既要有单元测试(快),也要有集成测试(准确)
|
|
744
|
+
|
|
745
|
+
### Q5: @SpringBootTest 为什么找不到主类?
|
|
746
|
+
|
|
747
|
+
**A:** `@SpringBootTest` 只能在 SpringBoot 主包下使用,测试类必须与主类(包含 main 方法)在同一个包或子包下。
|
|
748
|
+
|
|
749
|
+
### Q6: 如何跳过测试执行?
|
|
750
|
+
|
|
751
|
+
**A:**
|
|
752
|
+
- **单个测试**:使用 `@Disabled` 注解
|
|
753
|
+
- **Maven 跳过所有测试**:`mvn clean install -DskipTests`
|
|
754
|
+
|
|
755
|
+
---
|