kcode-pi 0.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.
Files changed (219) hide show
  1. package/README.md +358 -0
  2. package/dist/cli/kcode.d.ts +15 -0
  3. package/dist/cli/kcode.js +153 -0
  4. package/dist/cli/main.d.ts +2 -0
  5. package/dist/cli/main.js +7 -0
  6. package/docs/KCODE_DISTRIBUTION.md +91 -0
  7. package/extensions/kingdee-harness.ts +180 -0
  8. package/extensions/kingdee-header.ts +122 -0
  9. package/extensions/kingdee-tools.ts +379 -0
  10. package/knowledge/.backup/v1.0.0/version.json +10 -0
  11. package/knowledge/cangqiong/product-notes.md +15 -0
  12. package/knowledge/common/business-flows.md +115 -0
  13. package/knowledge/common/config-guides.md +110 -0
  14. package/knowledge/common/error-patterns.md +170 -0
  15. package/knowledge/common/implementation.md +144 -0
  16. package/knowledge/cosmic/hard-constraints.md +38 -0
  17. package/knowledge/cosmic/ksql-datafix.md +34 -0
  18. package/knowledge/cosmic/platform-baseline.md +32 -0
  19. package/knowledge/cosmic/plugin-decision-matrix.md +40 -0
  20. package/knowledge/cosmic/review-checklist.md +40 -0
  21. package/knowledge/cosmic/unittest.md +35 -0
  22. package/knowledge/enterprise/api-reference.md +186 -0
  23. package/knowledge/enterprise/code-patterns.md +217 -0
  24. package/knowledge/enterprise/plugin-lifecycle.md +188 -0
  25. package/knowledge/enterprise/tables.json +159 -0
  26. package/knowledge/flagship/api-reference.md +237 -0
  27. package/knowledge/flagship/code-patterns.md +246 -0
  28. package/knowledge/flagship/cosmic-platform-note.md +15 -0
  29. package/knowledge/flagship/plugin-lifecycle.md +248 -0
  30. package/knowledge/flagship/tables.json +159 -0
  31. package/knowledge/version.json +10 -0
  32. package/knowledge/xinghan/product-notes.md +15 -0
  33. package/package.json +71 -0
  34. package/prompts/kd-discuss.md +11 -0
  35. package/prompts/kd-execute.md +12 -0
  36. package/prompts/kd-plan.md +12 -0
  37. package/prompts/kd-ship.md +12 -0
  38. package/prompts/kd-spec.md +12 -0
  39. package/prompts/kd-verify.md +12 -0
  40. package/skills/kd-check/SKILL.md +26 -0
  41. package/skills/kd-cosmic-dev/SKILL.md +82 -0
  42. package/skills/kd-cosmic-review/SKILL.md +90 -0
  43. package/skills/kd-cosmic-unittest/SKILL.md +92 -0
  44. package/skills/kd-debug/SKILL.md +30 -0
  45. package/skills/kd-discuss/SKILL.md +24 -0
  46. package/skills/kd-execute/SKILL.md +22 -0
  47. package/skills/kd-gen/SKILL.md +34 -0
  48. package/skills/kd-ksql/SKILL.md +86 -0
  49. package/skills/kd-plan/SKILL.md +24 -0
  50. package/skills/kd-ship/SKILL.md +22 -0
  51. package/skills/kd-spec/SKILL.md +24 -0
  52. package/skills/kd-verify/SKILL.md +22 -0
  53. package/themes/kcode-dark.json +81 -0
  54. package/vendor/kingdee-skills/cosmic-unittest/SKILL.md +788 -0
  55. package/vendor/kingdee-skills/cosmic-unittest/author-cache.json +5 -0
  56. package/vendor/kingdee-skills/cosmic-unittest/cosmic-unittest-skill-overview.html +746 -0
  57. package/vendor/kingdee-skills/cosmic-unittest/examples/business-test.md +205 -0
  58. package/vendor/kingdee-skills/cosmic-unittest/examples/common-test.md +257 -0
  59. package/vendor/kingdee-skills/cosmic-unittest/examples/formplugin-test.md +560 -0
  60. package/vendor/kingdee-skills/cosmic-unittest/examples/op-plugin-test.md +231 -0
  61. package/vendor/kingdee-skills/cosmic-unittest/examples/validator-test.md +232 -0
  62. package/vendor/kingdee-skills/cosmic-unittest/patterns/business-helper.md +184 -0
  63. package/vendor/kingdee-skills/cosmic-unittest/patterns/common-module.md +355 -0
  64. package/vendor/kingdee-skills/cosmic-unittest/patterns/convert-plugin.md +130 -0
  65. package/vendor/kingdee-skills/cosmic-unittest/patterns/formplugin.md +235 -0
  66. package/vendor/kingdee-skills/cosmic-unittest/patterns/op-plugin.md +226 -0
  67. package/vendor/kingdee-skills/cosmic-unittest/patterns/validator.md +206 -0
  68. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +674 -0
  69. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/advanced-scenario-checklist.md +307 -0
  70. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/algox-performance-checklist.md +129 -0
  71. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/coding-standard-checklist.md +491 -0
  72. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/cosmic-api-checklist.md +285 -0
  73. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-access-checklist.md +261 -0
  74. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-transaction-checklist.md +390 -0
  75. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/domain-logic-checklist.md +295 -0
  76. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/form-plugin-checklist.md +508 -0
  77. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/infra-checklist.md +254 -0
  78. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ksql-checklist.md +305 -0
  79. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/lifecycle-checklist.md +298 -0
  80. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/operation-plugin-checklist.md +442 -0
  81. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/test-mock-checklist.md +120 -0
  82. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ui-performance-checklist.md +320 -0
  83. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +336 -0
  84. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +121 -0
  85. package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +295 -0
  86. package/vendor/kingdee-skills/ok-cosmic/README.md +460 -0
  87. package/vendor/kingdee-skills/ok-cosmic/SKILL.md +287 -0
  88. package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +17 -0
  89. package/vendor/kingdee-skills/ok-cosmic/assets/BatchImportPluginTemplate.java +93 -0
  90. package/vendor/kingdee-skills/ok-cosmic/assets/BillPlugInTemplate.java +156 -0
  91. package/vendor/kingdee-skills/ok-cosmic/assets/ConvertPlugInTemplate.java +255 -0
  92. package/vendor/kingdee-skills/ok-cosmic/assets/FormPluginTemplate.java +597 -0
  93. package/vendor/kingdee-skills/ok-cosmic/assets/IWorkflowPluginTemplate.java +91 -0
  94. package/vendor/kingdee-skills/ok-cosmic/assets/ListPluginTemplate.java +194 -0
  95. package/vendor/kingdee-skills/ok-cosmic/assets/OpPluginTemplate.java +201 -0
  96. package/vendor/kingdee-skills/ok-cosmic/assets/OpenApiControllerTemplate.java +103 -0
  97. package/vendor/kingdee-skills/ok-cosmic/assets/PrintPluginTemplate.java +95 -0
  98. package/vendor/kingdee-skills/ok-cosmic/assets/ReportFormPluginTemplate.java +257 -0
  99. package/vendor/kingdee-skills/ok-cosmic/assets/ReportListDataPluginTemplate.java +70 -0
  100. package/vendor/kingdee-skills/ok-cosmic/assets/StandardTreeListPluginTemplate.java +130 -0
  101. package/vendor/kingdee-skills/ok-cosmic/assets/TaskTemplate.java +80 -0
  102. package/vendor/kingdee-skills/ok-cosmic/assets/TreeListPluginTemplate.java +152 -0
  103. package/vendor/kingdee-skills/ok-cosmic/assets/WriteBackPlugInTemplate.java +286 -0
  104. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/attachment/AttachmentUploadBindSample.java +93 -0
  105. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/BotpTracePushSample.java +168 -0
  106. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/SampleConvertPlugin.java +223 -0
  107. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/cache/SampleCacheUsage.java +218 -0
  108. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/concurrent/SampleThreadPoolBatch.java +156 -0
  109. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectCrudSample.java +205 -0
  110. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectOpsSample.java +100 -0
  111. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/BeforeOperationConfirmSample.java +217 -0
  112. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ConfirmDialogSample.java +131 -0
  113. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/EntryRowCalculateSample.java +116 -0
  114. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/F7FilterSample.java +134 -0
  115. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/GetAndSetValueSample.java +176 -0
  116. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/HyperlinkJumpSample.java +124 -0
  117. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/OpenBillModalSample.java +253 -0
  118. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ReturnParentDataSample.java +295 -0
  119. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/TreeControlSample.java +140 -0
  120. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ViewControlOpsSample.java +132 -0
  121. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPluginBasicSample.java +170 -0
  122. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPreOpenFilterSample.java +68 -0
  123. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/message/MessageNotifySample.java +95 -0
  124. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/SampleMQConsumer.java +198 -0
  125. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/sample_mq.xml +15 -0
  126. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OpAddValidatorsSample.java +137 -0
  127. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OperationOptionBridgeSample.java +228 -0
  128. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/package-info.java +19 -0
  129. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BaseDataQuerySample.java +194 -0
  130. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java +368 -0
  131. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/DataSetQueryStatSample.java +131 -0
  132. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportFormPlugin.java +179 -0
  133. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportListDataPlugin.java +616 -0
  134. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/snippets-guide.md +64 -0
  135. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/task/ScheduleTaskSample.java +160 -0
  136. package/vendor/kingdee-skills/ok-cosmic/assets/snippets/workflow/SampleWorkflowPlugin.java +302 -0
  137. package/vendor/kingdee-skills/ok-cosmic/manifest.json +78 -0
  138. package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +903 -0
  139. package/vendor/kingdee-skills/ok-cosmic/references/adv/attachment-api.md +114 -0
  140. package/vendor/kingdee-skills/ok-cosmic/references/adv/botp-convert.md +98 -0
  141. package/vendor/kingdee-skills/ok-cosmic/references/adv/dynamic-object.md +113 -0
  142. package/vendor/kingdee-skills/ok-cosmic/references/adv/entity-metadata.md +123 -0
  143. package/vendor/kingdee-skills/ok-cosmic/references/adv/event-lifecycle.md +184 -0
  144. package/vendor/kingdee-skills/ok-cosmic/references/adv/flex-prop.md +114 -0
  145. package/vendor/kingdee-skills/ok-cosmic/references/adv/form-utils.md +133 -0
  146. package/vendor/kingdee-skills/ok-cosmic/references/adv/operate-chain.md +159 -0
  147. package/vendor/kingdee-skills/ok-cosmic/references/adv/plugin-base.md +218 -0
  148. package/vendor/kingdee-skills/ok-cosmic/references/adv/query-dataset.md +149 -0
  149. package/vendor/kingdee-skills/ok-cosmic/references/adv/request-context.md +88 -0
  150. package/vendor/kingdee-skills/ok-cosmic/references/adv/view-handler.md +157 -0
  151. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-bill.md +76 -0
  152. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-botp.md +70 -0
  153. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-form.md +165 -0
  154. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-import.md +69 -0
  155. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-list.md +227 -0
  156. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-openapi.md +112 -0
  157. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-operation.md +135 -0
  158. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-print.md +65 -0
  159. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-data.md +64 -0
  160. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-form.md +90 -0
  161. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-task.md +62 -0
  162. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-tree-list.md +71 -0
  163. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-workflow.md +82 -0
  164. package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-writeback.md +71 -0
  165. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-algo.md +67 -0
  166. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-cache.md +63 -0
  167. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-model-svc.md +82 -0
  168. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-object.md +70 -0
  169. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-entity-model.md +61 -0
  170. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-exception.md +64 -0
  171. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-file.md +63 -0
  172. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-id.md +47 -0
  173. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-lock.md +61 -0
  174. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-log.md +63 -0
  175. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-network-control.md +70 -0
  176. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-orm-access.md +78 -0
  177. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-request-context.md +62 -0
  178. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-threadpool.md +63 -0
  179. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-tx.md +64 -0
  180. package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-utils.md +67 -0
  181. package/vendor/kingdee-skills/ok-cosmic/requirements.txt +2 -0
  182. package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +24 -0
  183. package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +48 -0
  184. package/vendor/kingdee-skills/ok-cosmic/rules/cheat-sheet.md +256 -0
  185. package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +140 -0
  186. package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +61 -0
  187. package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +222 -0
  188. package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +94 -0
  189. package/vendor/kingdee-skills/ok-cosmic/rules/platform-baseline.md +69 -0
  190. package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +109 -0
  191. package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +204 -0
  192. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +910 -0
  193. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +359 -0
  194. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +181 -0
  195. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +389 -0
  196. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +856 -0
  197. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +262 -0
  198. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +293 -0
  199. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +2 -0
  200. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +393 -0
  201. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +176 -0
  202. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +375 -0
  203. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +434 -0
  204. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +36 -0
  205. package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +186 -0
  206. package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +40 -0
  207. package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +142 -0
  208. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
  209. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
  210. package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic-docs.db +0 -0
  211. package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic.json +13 -0
  212. package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +18 -0
  213. package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +53 -0
  214. package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
  215. package/vendor/kingdee-skills/ok-ksql/SKILL.md +81 -0
  216. package/vendor/kingdee-skills/ok-ksql/agents/openai.yaml +7 -0
  217. package/vendor/kingdee-skills/ok-ksql/manifest.json +14 -0
  218. package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +452 -0
  219. package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +363 -0
@@ -0,0 +1,746 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>cosmic-unittest 技能总览 v1.4</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: #f0f2f5; color: #24292e; font-size: 14px; line-height: 1.6; }
10
+ .header { background: linear-gradient(135deg, #1a1f2e 0%, #2d3a5c 50%, #1e4080 100%); color: white; padding: 32px 40px; }
11
+ .header h1 { font-size: 26px; font-weight: 700; margin-bottom: 6px; letter-spacing: 1px; }
12
+ .header .subtitle { font-size: 14px; opacity: 0.8; }
13
+ .badge { display: inline-block; background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.3); border-radius: 12px; padding: 2px 10px; font-size: 12px; margin-left: 10px; vertical-align: middle; }
14
+
15
+ .main { max-width: 1400px; margin: 0 auto; padding: 24px 32px; }
16
+ .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
17
+ .grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 20px; }
18
+ .grid-4 { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 20px; margin-bottom: 20px; }
19
+ .full-width { margin-bottom: 20px; }
20
+
21
+ .card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); }
22
+ .card h2 { font-size: 15px; font-weight: 700; color: #1a1f2e; margin-bottom: 14px; padding-bottom: 8px; border-bottom: 2px solid #e1e4e8; display: flex; align-items: center; gap: 8px; }
23
+ .card h3 { font-size: 13px; font-weight: 600; color: #444d56; margin: 12px 0 6px; }
24
+
25
+ .step-flow { display: flex; align-items: center; gap: 0; flex-wrap: wrap; }
26
+ .step-box { background: #f6f8fa; border: 1px solid #d1d5da; border-radius: 6px; padding: 8px 14px; text-align: center; min-width: 80px; }
27
+ .step-box .step-num { font-size: 10px; color: #6a737d; font-weight: 600; text-transform: uppercase; }
28
+ .step-box .step-name { font-size: 13px; font-weight: 600; color: #1a1f2e; margin-top: 2px; }
29
+ .step-box .step-desc { font-size: 11px; color: #586069; margin-top: 2px; }
30
+ .step-arrow { font-size: 18px; color: #aaa; padding: 0 6px; }
31
+ .step-box.highlight { background: #0366d6; border-color: #0366d6; color: white; }
32
+ .step-box.highlight .step-num, .step-box.highlight .step-name, .step-box.highlight .step-desc { color: white; }
33
+
34
+ .tree-item { padding: 4px 0; display: flex; align-items: flex-start; gap: 6px; font-size: 13px; }
35
+ .tree-item .icon { width: 18px; height: 18px; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 10px; flex-shrink: 0; margin-top: 1px; font-weight: 700; }
36
+ .tree-sub { margin-left: 22px; }
37
+ .tree-sub .tree-item { font-size: 12px; color: #586069; }
38
+
39
+ .badge-blue { background: #dbeafe; color: #1d4ed8; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
40
+ .badge-green { background: #dcfce7; color: #15803d; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
41
+ .badge-orange { background: #fef3c7; color: #d97706; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
42
+ .badge-red { background: #fee2e2; color: #dc2626; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
43
+ .badge-purple { background: #ede9fe; color: #7c3aed; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
44
+ .badge-gray { background: #f1f3f5; color: #495057; border-radius: 4px; padding: 1px 7px; font-size: 11px; font-weight: 600; white-space: nowrap; }
45
+
46
+ code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 12px; background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 3px; padding: 1px 5px; }
47
+ pre { background: #1e2433; color: #e6edf3; border-radius: 6px; padding: 14px 16px; font-size: 12px; line-height: 1.7; overflow-x: auto; margin-top: 8px; font-family: "SFMono-Regular", Consolas, monospace; }
48
+ pre .kw { color: #ff7b72; }
49
+ pre .str { color: #a5d6ff; }
50
+ pre .cmt { color: #8b949e; font-style: italic; }
51
+ pre .ann { color: #ffa657; }
52
+ pre .cls { color: #79c0ff; }
53
+ pre .num { color: #79c0ff; }
54
+
55
+ table { width: 100%; border-collapse: collapse; font-size: 12px; }
56
+ thead tr { background: #f6f8fa; }
57
+ th { padding: 7px 10px; text-align: left; font-weight: 600; color: #444d56; border-bottom: 2px solid #d1d5da; white-space: nowrap; }
58
+ td { padding: 6px 10px; border-bottom: 1px solid #e1e4e8; vertical-align: top; }
59
+ tr:last-child td { border-bottom: none; }
60
+ tr:hover td { background: #f6f8fa; }
61
+
62
+ .rule-item { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; border-bottom: 1px solid #e1e4e8; font-size: 12px; }
63
+ .rule-item:last-child { border-bottom: none; }
64
+ .rule-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
65
+ .rule-text strong { color: #1a1f2e; }
66
+ .rule-text span { color: #586069; }
67
+
68
+ .warn-box { background: #fff8e1; border-left: 4px solid #f59e0b; border-radius: 0 6px 6px 0; padding: 10px 14px; margin-top: 8px; font-size: 12px; }
69
+ .warn-box strong { color: #b45309; }
70
+ .ok-box { background: #f0fff4; border-left: 4px solid #10b981; border-radius: 0 6px 6px 0; padding: 10px 14px; margin-top: 8px; font-size: 12px; }
71
+ .ok-box strong { color: #047857; }
72
+
73
+ .module-card { border: 1px solid #e1e4e8; border-radius: 6px; padding: 12px; margin-bottom: 10px; }
74
+ .module-card:last-child { margin-bottom: 0; }
75
+ .module-title { font-size: 13px; font-weight: 700; margin-bottom: 6px; }
76
+ .module-meta { font-size: 11px; color: #586069; }
77
+
78
+ .confusion-row-bad { background: #fff5f5; }
79
+ .confusion-row-good { background: #f0fff4; }
80
+ .x-mark { color: #dc2626; font-weight: 700; }
81
+ .check-mark { color: #16a34a; font-weight: 700; }
82
+
83
+ .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
84
+ .section-title { font-size: 16px; font-weight: 700; color: #1a1f2e; margin: 8px 0 16px; padding-left: 10px; border-left: 4px solid #0366d6; }
85
+ </style>
86
+ </head>
87
+ <body>
88
+
89
+ <div class="header">
90
+ <h1>cosmic-unittest 苍穹模块单元测试编写技能 <span class="badge">v1.4</span></h1>
91
+ <div class="subtitle">适用于任何遵循 common / business / opplugin / formplugin 四工程结构的金蝶云苍穹模块 &nbsp;·&nbsp; JUnit 4 + Mockito 2.x &nbsp;·&nbsp; 6 项性能优化</div>
92
+ </div>
93
+
94
+ <div class="main">
95
+
96
+ <!-- 执行流程 -->
97
+ <div class="full-width">
98
+ <p class="section-title">技能执行流程</p>
99
+ <div class="card">
100
+ <div class="step-flow">
101
+ <div class="step-box">
102
+ <div class="step-num">Step 0.0</div>
103
+ <div class="step-name">识别模块组</div>
104
+ <div class="step-desc">从包名推导前缀</div>
105
+ </div>
106
+ <div class="step-arrow">→</div>
107
+ <div class="step-box">
108
+ <div class="step-num">Step 0.1</div>
109
+ <div class="step-name">判断测试类型</div>
110
+ <div class="step-desc">工程 → 模式</div>
111
+ </div>
112
+ <div class="step-arrow">→</div>
113
+ <div class="step-box highlight">
114
+ <div class="step-num">Step 0.5</div>
115
+ <div class="step-name">获取 Author</div>
116
+ <div class="step-desc">缓存 → git → 询问</div>
117
+ </div>
118
+ <div class="step-arrow">→</div>
119
+ <div class="step-box">
120
+ <div class="step-num">Step 1</div>
121
+ <div class="step-name">分析被测类</div>
122
+ <div class="step-desc">依赖 + Bug 审查</div>
123
+ </div>
124
+ <div class="step-arrow">→</div>
125
+ <div class="step-box">
126
+ <div class="step-num">Step 2</div>
127
+ <div class="step-name">编写测试类</div>
128
+ <div class="step-desc">注解 + Mock + 断言</div>
129
+ </div>
130
+ <div class="step-arrow">→</div>
131
+ <div class="step-box">
132
+ <div class="step-num">Step 3</div>
133
+ <div class="step-name">覆盖场景</div>
134
+ <div class="step-desc">边界值 + 分支</div>
135
+ </div>
136
+ <div class="step-arrow">→</div>
137
+ <div class="step-box">
138
+ <div class="step-num">Step 4</div>
139
+ <div class="step-name">验证运行</div>
140
+ <div class="step-desc">静态分析 + Gradle</div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- Step 0.1 + v1.4 性能优化专区 -->
147
+ <div class="full-width">
148
+ <p class="section-title">⚡ v1.4 性能优化专区</p>
149
+ <div class="grid-3">
150
+ <div class="card">
151
+ <h2>📂 按需加载 pattern 文件</h2>
152
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">根据工程类型只读取 <strong>1 个</strong> pattern,禁止全量加载:</p>
153
+ <table>
154
+ <thead><tr><th>工程类型</th><th>pattern 文件</th></tr></thead>
155
+ <tbody>
156
+ <tr><td>common - POJO</td><td><code>common-module.md #POJO</code></td></tr>
157
+ <tr><td>common - 枚举</td><td><code>common-module.md #Enum</code></td></tr>
158
+ <tr><td>common - 工具类</td><td><code>common-module.md #Utils</code></td></tr>
159
+ <tr><td>business</td><td><code>business-helper.md</code></td></tr>
160
+ <tr><td>opplugin - Validator</td><td><code>validator.md</code></td></tr>
161
+ <tr><td>opplugin - OpPlugin</td><td><code>op-plugin.md</code></td></tr>
162
+ <tr><td>opplugin - Convert</td><td><code>convert-plugin.md</code></td></tr>
163
+ <tr><td>formplugin</td><td><code>formplugin.md</code></td></tr>
164
+ </tbody>
165
+ </table>
166
+ <div class="ok-box">example 文件仅在需要参考具体写法时按需读取,非必读。</div>
167
+ </div>
168
+ <div class="card">
169
+ <h2>🚀 并行全量读取</h2>
170
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">读取被测类源码后,以下全部<strong>同一轮并行发起</strong>:</p>
171
+ <div class="rule-item">
172
+ <span class="rule-icon">📄</span>
173
+ <div class="rule-text"><strong>常量/枚举类</strong> <span>import 提取 → grep_code 并行搜索</span></div>
174
+ </div>
175
+ <div class="rule-item">
176
+ <span class="rule-icon">📄</span>
177
+ <div class="rule-text"><strong>BaseTest 源码</strong> <span>search_file 搜索确认预置 Mock</span></div>
178
+ </div>
179
+ <div class="rule-item">
180
+ <span class="rule-icon">📄</span>
181
+ <div class="rule-text"><strong>对应 pattern</strong> <span>直接 read_file 补读</span></div>
182
+ </div>
183
+ <div class="rule-item">
184
+ <span class="rule-icon">📄</span>
185
+ <div class="rule-text"><strong>关联类签名</strong> <span>import 提取 → grep_code 搜索方法签名</span></div>
186
+ </div>
187
+ <div class="warn-box" style="margin-top:8px;">v1.3 只并行读取常量,v1.4 扩展为全量并行,减少多轮串行等待。</div>
188
+ </div>
189
+ <div class="card">
190
+ <h2>🧠 智能 Mock 推断</h2>
191
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">从 import 自动匹配映射表,规则驱动取代经验驱动:</p>
192
+ <table>
193
+ <thead><tr><th>import 中的类</th><th>Mock 方式</th></tr></thead>
194
+ <tbody>
195
+ <tr><td><code>QueryServiceHelper</code></td><td>mockStatic + when(query)</td></tr>
196
+ <tr><td><code>BusinessDataServiceHelper</code></td><td>mockStatic + when(load...)</td></tr>
197
+ <tr><td><code>OperationServiceHelper</code></td><td>mockStatic + when(executeOperate)</td></tr>
198
+ <tr><td><code>SaveServiceHelper</code></td><td>mockStatic + when(save/update)</td></tr>
199
+ <tr><td><code>OrgUnitServiceHelper</code></td><td>mockStatic(通常无需 stub)</td></tr>
200
+ <tr><td><code>ResManager</code></td><td>mockStatic + thenAnswer(getArg(0))</td></tr>
201
+ <tr><td><code>DB</code></td><td style="color:#dc2626;">必须先 ClassPool defrost</td></tr>
202
+ <tr><td><code>ProcessSettleHelper</code></td><td style="color:#dc2626;">禁止直接 mockStatic,用 MockHelper</td></tr>
203
+ </tbody>
204
+ </table>
205
+ </div>
206
+ </div>
207
+ <div class="grid-2" style="margin-top:20px;">
208
+ <div class="card">
209
+ <h2>🧹 BaseTest Mock 继承去重</h2>
210
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">仅声明 BaseTest 未预置的 MockedStatic <strong>差集</strong>:</p>
211
+ <pre><span class="kw">public class</span> <span class="cls">SomeHelperTest</span> <span class="kw">extends</span> <span class="cls">BaseTest</span> {
212
+ <span class="cmt">// 仅声明 BaseTest 未预置的</span>
213
+ <span class="cls">MockedStatic</span>&lt;<span class="cls">ProcessPlanHelper</span>&gt; planHelper;
214
+
215
+ <span class="ann">@Before</span> <span class="kw">public void</span> before() {
216
+ <span class="kw">super</span>.before(); <span class="cmt">// 先触发 BaseTest</span>
217
+ planHelper = mockStatic(<span class="cls">ProcessPlanHelper</span>.class);
218
+ }
219
+ <span class="ann">@After</span> <span class="kw">public void</span> after() {
220
+ planHelper.close();
221
+ <span class="kw">super</span>.after();
222
+ }
223
+ }</pre>
224
+ </div>
225
+ <div class="card">
226
+ <h2>📦 批量模式并行策略</h2>
227
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">同工程多被测类,最大化并行:</p>
228
+ <table>
229
+ <thead><tr><th>步骤</th><th>并行策略</th></tr></thead>
230
+ <tbody>
231
+ <tr><td>源码读取</td><td>所有被测类 + 常量 + BaseTest <strong>同一轮并行</strong></td></tr>
232
+ <tr><td>逻辑分析</td><td>逐类分析,汇总输出一次(跳过确认)</td></tr>
233
+ <tr><td>代码生成</td><td>互不依赖的测试类可<strong>并行生成</strong></td></tr>
234
+ <tr><td>Gradle 验证</td><td>只冷启动<strong>一次</strong>,合并运行</td></tr>
235
+ </tbody>
236
+ </table>
237
+ <div class="ok-box" style="margin-top:8px;">核心价值:Gradle 冷启动一次 + 文件读取并行化 + 测试类并行生成</div>
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ <!-- Step 0.0 + Step 0.1 -->
243
+ <div class="grid-2">
244
+ <div class="card">
245
+ <h2>📦 Step 0.0 — 识别模块组前缀</h2>
246
+ <p style="font-size:12px;color:#586069;margin-bottom:10px;">从被测类的包名或文件路径中提取 <code>&lt;模块组&gt;</code>,后续所有工程名、包名、路径均基于此推导。</p>
247
+ <table>
248
+ <thead><tr><th>包名示例</th><th>模块组前缀</th></tr></thead>
249
+ <tbody>
250
+ <tr><td><code>kd.mmc.sfc.business.XxxHelper</code></td><td><span class="badge-blue">mmc-sfc</span></td></tr>
251
+ <tr><td><code>kd.bd.mpdm.business.XxxHelper</code></td><td><span class="badge-blue">bd-mpdm</span></td></tr>
252
+ <tr><td><code>kd.sys.xkbase.business.XxxHelper</code></td><td><span class="badge-blue">sys-xkbase</span></td></tr>
253
+ <tr><td><code>kd.mmc.pom.business.XxxHelper</code></td><td><span class="badge-blue">mmc-pom</span></td></tr>
254
+ </tbody>
255
+ </table>
256
+ </div>
257
+ <div class="card">
258
+ <h2>🏗️ Step 0.1 — 工程 → 测试模式</h2>
259
+ <div class="module-card">
260
+ <div class="module-title"><span class="badge-purple">&lt;模块组&gt;-common</span> &nbsp; 公共模块</div>
261
+ <div class="tree-sub">
262
+ <div class="tree-item"><span style="color:#7c3aed">●</span> POJO/DTO/Bean → <span class="badge-gray">POJO 模式</span> 无需 Mock 直接 new</div>
263
+ <div class="tree-item"><span style="color:#7c3aed">●</span> 枚举类(含业务方法)→ <span class="badge-gray">Enum 模式</span> 继承 AbstractJunitNoDependenciesTest</div>
264
+ <div class="tree-item"><span style="color:#7c3aed">●</span> 工具类 XxxUtil/XxxUtils → <span class="badge-gray">Utils 模式</span> 继承 BaseTest</div>
265
+ </div>
266
+ </div>
267
+ <div class="module-card">
268
+ <div class="module-title"><span class="badge-blue">&lt;模块组&gt;-business</span> &nbsp; 业务帮助类</div>
269
+ <div class="module-meta">XxxHelper / XxxService → Business Helper 模式,继承 BaseTest 或 AbstractJunitNoDependenciesResManagerTest</div>
270
+ </div>
271
+ <div class="module-card">
272
+ <div class="module-title"><span class="badge-orange">&lt;模块组&gt;-opplugin</span> &nbsp; 操作插件工程</div>
273
+ <div class="tree-sub">
274
+ <div class="tree-item"><span style="color:#d97706">●</span> Validator → <span class="badge-gray">Validator 模式</span></div>
275
+ <div class="tree-item"><span style="color:#d97706">●</span> AbstractOperationServicePlugIn → <span class="badge-gray">OpPlugin 模式</span></div>
276
+ <div class="tree-item"><span style="color:#d97706">●</span> ConvertPlugin/BotpPlugin → <span class="badge-gray">ConvertPlugin 模式</span></div>
277
+ </div>
278
+ </div>
279
+ <div class="module-card">
280
+ <div class="module-title"><span class="badge-green">&lt;模块组&gt;-formplugin</span> &nbsp; 表单插件工程</div>
281
+ <div class="module-meta">继承 AbstractFormPlugin/ListPlugin/BillPlugin → FormPlugin 模式,优先继承 BasePluginTest&lt;T&gt;</div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <!-- Step 0.5 Author + Step 1 分析 -->
287
+ <div class="grid-2">
288
+ <div class="card">
289
+ <h2>👤 Step 0.5 — 获取 Author 信息(三级优先级)</h2>
290
+ <div class="rule-item">
291
+ <span class="rule-icon">1️⃣</span>
292
+ <div class="rule-text"><strong>缓存文件</strong> <span>— 读取 <code>.qoder/skills/cosmic-unittest/author-cache.json</code>,若 author 非空直接使用,跳过 git 命令</span></div>
293
+ </div>
294
+ <div class="rule-item">
295
+ <span class="rule-icon">2️⃣</span>
296
+ <div class="rule-text"><strong>git config</strong> <span>— 缓存不存在或 author 为空时,执行 <code>git config user.name</code> + <code>git config user.email</code>,读取成功后写入缓存</span></div>
297
+ </div>
298
+ <div class="rule-item">
299
+ <span class="rule-icon">3️⃣</span>
300
+ <div class="rule-text"><strong>询问用户</strong> <span>— git config 读取失败时,主动询问用户提供姓名和邮箱,填入缓存</span></div>
301
+ </div>
302
+ <div class="ok-box" style="margin-top:10px;">
303
+ <strong>格式规范:</strong><code>&lt;name&gt; &lt;&lt;name&gt;@kingdee.com&gt;</code><br>
304
+ 例如:<code>zhang_san &lt;zhang_san@kingdee.com&gt;</code>
305
+ </div>
306
+ <div class="warn-box">
307
+ 首次写入 <code>author-cache.json</code> 后,检查 <code>.gitignore</code> 是否已包含该文件的忽略规则,避免个人信息提交到 git。
308
+ </div>
309
+ </div>
310
+ <div class="card">
311
+ <h2>🔍 Step 1 — 分析被测类</h2>
312
+ <div class="rule-item">
313
+ <span class="rule-icon">1️⃣</span>
314
+ <div class="rule-text"><strong>读取完整源码</strong> <span>— 识别所有 public/protected 方法</span></div>
315
+ </div>
316
+ <div class="rule-item">
317
+ <span class="rule-icon">2️⃣</span>
318
+ <div class="rule-text"><strong>依赖识别</strong> <span>— 静态方法(MockedStatic)、外部服务、DynamicObject 结构</span></div>
319
+ </div>
320
+ <div class="rule-item">
321
+ <span class="rule-icon">3️⃣</span>
322
+ <div class="rule-text"><strong>分支识别</strong> <span>— switch 分支 / if-else 判断分支(用于 Step 3 拆分策略)</span></div>
323
+ </div>
324
+ <div class="rule-item">
325
+ <span class="rule-icon">4️⃣</span>
326
+ <div class="rule-text"><strong>Bug 审查</strong> <span>— 边界方向错误、空指针风险、逻辑取反、计算错误、集合遗漏</span></div>
327
+ </div>
328
+ <div class="rule-item" style="background:#eff6ff;border-radius:6px;padding:8px;">
329
+ <span class="rule-icon">⚡</span>
330
+ <div class="rule-text"><strong>v1.4 并行全量读取</strong> <span>— 常量类 + BaseTest + pattern + 关联类方法签名,同一轮并行拿齐</span></div>
331
+ </div>
332
+ <div class="rule-item" style="background:#eff6ff;border-radius:6px;padding:8px;">
333
+ <span class="rule-icon">⚡</span>
334
+ <div class="rule-text"><strong>v1.4 智能 Mock 推断</strong> <span>— 从 import 自动匹配「import → Mock 映射表」,规则驱动取代经验驱动</span></div>
335
+ </div>
336
+ <div class="warn-box">
337
+ <strong>强制输出:</strong>分析完成后必须输出「被测逻辑分析」摘要(含 Bug 检查结论),<strong>不得跳过直接给代码</strong>。<br>
338
+ 发现 Bug → 测试断言反映正确预期(让测试失败暴露 Bug)+ 注释 <code>// ⚠️ BUG: ...</code> + 告知用户。
339
+ </div>
340
+ </div>
341
+ </div>
342
+
343
+ <!-- Step 2 编写规则 -->
344
+ <div class="full-width">
345
+ <p class="section-title">Step 2 — 编写测试类</p>
346
+ <div class="grid-2">
347
+ <div class="card">
348
+ <h2>⚙️ 2.1 通用编写规则</h2>
349
+ <table>
350
+ <thead><tr><th>规则</th><th>说明</th></tr></thead>
351
+ <tbody>
352
+ <tr><td>测试框架</td><td>JUnit 4(<code>@Test</code> / <code>@Before</code> / <code>@After</code>)</td></tr>
353
+ <tr><td>Mock 框架</td><td>Mockito 2.x + <code>MockedStatic</code></td></tr>
354
+ <tr><td><strong>Mockito 静态导入</strong></td><td>❌ 禁用通配符 <code>import static ...ArgumentMatchers.*</code><br>✅ 必须显式逐个导入:<code>any</code> / <code>anyBoolean</code> / <code>anyInt</code> / <code>anyLong</code> / <code>anyString</code> / <code>eq</code> / <code>isNull</code>(ArgumentMatchers);<code>atLeastOnce</code> / <code>mock</code> / <code>never</code> / <code>verify</code> / <code>when</code>(Mockito)</td></tr>
355
+ <tr><td>数据构造</td><td><code>DynamicObjectMocker</code> 链式 <code>.add(key, value)</code></td></tr>
356
+ <tr><td>基类选择</td><td>无 ResManager → <code>AbstractJunitNoDependenciesTest</code><br>有 ResManager → <code>AbstractJunitNoDependenciesResManagerTest</code><br>多依赖 → 继承工程 <code>BaseTest</code></td></tr>
357
+ <tr><td>反射工具</td><td><code>ReflectHelper.invokeProtectedMethod()</code><br><code>ReflectHelper.invokeStaticMethod()</code></td></tr>
358
+ <tr><td>资源释放</td><td>所有 MockedStatic 必须在 <code>@After</code> 中 <code>.close()</code></td></tr>
359
+ <tr><td>注解要求</td><td>每个 <code>@Test</code> 方法<strong>必须</strong>加 <code>@UnittestCaseInfo</code> 和 <code>@DisplayName</code></td></tr>
360
+ <tr><td><strong>断言质量</strong></td><td>❌ 禁止假断言(<code>assertTrue(true)</code> 等)<br>❌ 禁止无断言测试方法<br>✅ 每个 @Test 至少一条有效 assert/verify</td></tr>
361
+ <tr><td>switch 策略</td><td>每个 case 必须拆为<strong>独立测试方法</strong></td></tr>
362
+ <tr><td>if/else 覆盖</td><td>尽量覆盖所有条件组合(笛卡尔积),可合并到同一方法</td></tr>
363
+ <tr><td>常量/枚举引用</td><td>找不到时<strong>主动告知用户</strong>,<strong>禁止字面值替代</strong></td></tr>
364
+ <tr style="background:#eff6ff;"><td><strong>⚡ BaseTest Mock 去重</strong></td><td>不得重复声明 BaseTest 已预置的 MockedStatic,仅声明「候选 Mock 类 - BaseTest 已预置」的<strong>差集</strong><br>@Before 中先 <code>super.before()</code> 再初始化自有 Mock</td></tr>
365
+ </tbody>
366
+ </table>
367
+ </div>
368
+ <div class="card">
369
+ <h2>📝 2.2 @UnittestCaseInfo 注解规范</h2>
370
+ <pre><span class="ann">@UnittestCaseInfo</span>(
371
+ <span class="str">author</span> = <span class="str">"&lt;name&gt; &lt;&lt;name&gt;@kingdee.com&gt;"</span>,
372
+ <span class="cmt">// git config user.name 拼接邮箱</span>
373
+ <span class="str">title</span> = <span class="str">"英文标题(简短描述测试目的)"</span>,
374
+ <span class="str">targetClass</span> = <span class="str">"被测类全限定名"</span>,
375
+ <span class="str">targetMethod</span> = <span class="str">"被测方法名(小写)"</span>,
376
+ <span class="str">lastUpdateTime</span> = <span class="str">"yyyy-MM-dd HH:mm:ss"</span>,
377
+ <span class="str">lastUpdateAuthor</span> = <span class="str">"&lt;name&gt; &lt;&lt;name&gt;@kingdee.com&gt;"</span>,
378
+ <span class="str">methodSignature</span> = <span class="str">"被测方法完整签名"</span>,
379
+ <span class="str">testPoints</span> = {<span class="str">"Functionality"</span>},
380
+ <span class="str">description</span> = <span class="str">"英文描述"</span>
381
+ )</pre>
382
+ <h3>2.3 测试方法注释规范</h3>
383
+ <pre><span class="ann">@Test</span>
384
+ <span class="ann">@DisplayName</span>(<span class="str">"中文描述:测试场景说明"</span>)
385
+ <span class="kw">public void</span> <span class="cls">testMethodName_ScenarioDescription</span>() {
386
+ <span class="cmt">//step 准备测试数据 / 构建 Mock</span>
387
+ ...
388
+ <span class="cmt">//step 执行被测方法</span>
389
+ ...
390
+ <span class="cmt">//assert 断言结果</span>
391
+ ...
392
+ reset(); <span class="cmt">// 如有 reset 方法</span>
393
+ }</pre>
394
+ <div style="margin-top:10px;font-size:12px;color:#586069;">
395
+ <code>DisplayName</code> 来自 <code>kd.bos.form.unittest.DisplayName</code>(非 org.junit.DisplayName)
396
+ </div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+
401
+ <!-- Step 2 特殊Mock -->
402
+ <div class="full-width">
403
+ <div class="grid-3">
404
+ <div class="card">
405
+ <h2>🔧 2.4–2.5 MockedStatic &amp; DynamicObjectMocker</h2>
406
+ <h3>MockedStatic 生命周期</h3>
407
+ <pre><span class="cmt">// 成员变量声明</span>
408
+ <span class="cls">MockedStatic</span>&lt;<span class="cls">XxxHelper</span>&gt; xxxHelper;
409
+
410
+ <span class="ann">@Before</span>
411
+ <span class="kw">public void</span> before() {
412
+ xxxHelper = mockStatic(<span class="cls">XxxHelper</span>.class);
413
+ }
414
+ <span class="ann">@After</span>
415
+ <span class="kw">public void</span> after() {
416
+ xxxHelper.close(); <span class="cmt">// 必须关闭!</span>
417
+ }
418
+ <span class="kw">public void</span> reset() {
419
+ xxxHelper.reset();
420
+ }</pre>
421
+ <h3 style="margin-top:12px;">DynamicObjectMocker 链式构造</h3>
422
+ <pre><span class="cmt">// 简单对象</span>
423
+ <span class="cls">DynamicObject</span> obj = <span class="kw">new</span> <span class="cls">DynamicObjectMocker</span>()
424
+ .add(<span class="str">"id"</span>, <span class="num">1L</span>)
425
+ .add(<span class="str">"billno"</span>, <span class="str">"TEST-001"</span>)
426
+ .getObject();
427
+
428
+ <span class="cmt">// 分录集合(用 getCollection)</span>
429
+ <span class="cls">DynamicObject</span> bill = <span class="kw">new</span> <span class="cls">DynamicObjectMocker</span>()
430
+ .add(<span class="str">"entryentity"</span>, <span class="kw">new</span> <span class="cls">DynamicObjectMocker</span>()
431
+ .add(<span class="str">"qty"</span>, <span class="cls">BigDecimal</span>.TEN)
432
+ .getCollection())
433
+ .getObject();</pre>
434
+ </div>
435
+ <div class="card">
436
+ <h2>⚠️ 2.6 DB 类特殊 Mock(ClassPool Defrost)</h2>
437
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">DB 类存在字节码冻结问题,mock 前必须先执行 defrost 处理:</p>
438
+ <pre><span class="ann">@Before</span>
439
+ <span class="kw">public void</span> before() {
440
+ <span class="kw">try</span> {
441
+ <span class="cls">CtClass</span> cls = <span class="cls">ClassPool</span>.getDefault()
442
+ .get(<span class="cls">DB</span>.class.getName());
443
+ <span class="kw">if</span> (cls.isFrozen()) {
444
+ cls.defrost();
445
+ }
446
+ } <span class="kw">catch</span> (<span class="cls">Exception</span> e) {}
447
+ dbMocked = <span class="cls">Mockito</span>.mockStatic(<span class="cls">DB</span>.class);
448
+ }</pre>
449
+ <h2 style="margin-top:16px;">⚠️ 2.7 ProcessSettleHelper 特殊 Mock</h2>
450
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">含分布式缓存,不可直接 mockStatic:</p>
451
+ <pre><span class="cls">MockedStatic</span>&lt;<span class="cls">ProcessSettleHelper</span>&gt; mock =
452
+ <span class="cls">ProcessSettleMockHelper</span>
453
+ .mockDistributeSessionlessCache();</pre>
454
+ <div class="warn-box">
455
+ 在<strong>当前被测类所属工程</strong>的 <code>src/test/java/</code> 下查找 <code>ProcessSettleMockHelper</code>。找不到则主动告知用户在对应工程 test 目录下添加。
456
+ </div>
457
+ </div>
458
+ <div class="card">
459
+ <h2>🏗️ 2.8–2.10 高级 Mock 技巧</h2>
460
+ <h3>MockedConstruction(Mock 构造函数)</h3>
461
+ <pre><span class="ann">@Test</span>
462
+ <span class="kw">public void</span> testMethod() {
463
+ <span class="kw">try</span> (<span class="cls">MockedConstruction</span>&lt;<span class="cls">PushArgs</span>&gt; m =
464
+ <span class="cls">Mockito</span>.mockConstruction(<span class="cls">PushArgs</span>.class)) {
465
+ <span class="cls">SomeUtil</span>.doSomething(params);
466
+ assertEquals(<span class="num">1</span>, m.constructed().size());
467
+ } <span class="cmt">// 自动关闭</span>
468
+ }</pre>
469
+ <h3>thenAnswer 动态返回</h3>
470
+ <pre>helper.when(() -> <span class="cls">QueryServiceHelper</span>
471
+ .query(anyString(), anyString(), any()))
472
+ .thenAnswer(inv -> {
473
+ <span class="cls">String</span> entity = inv.getArgument(<span class="num">0</span>);
474
+ <span class="kw">if</span> (<span class="str">"mmc_plan"</span>.equals(entity))
475
+ <span class="kw">return</span> mockPlanData();
476
+ <span class="kw">return new</span> <span class="cls">DynamicObjectCollection</span>();
477
+ });</pre>
478
+ <h3>DynamicObject 断言规范</h3>
479
+ <table>
480
+ <thead><tr><th>方法</th><th>默认值</th><th>断言</th></tr></thead>
481
+ <tbody>
482
+ <tr><td><code>getString()</code></td><td><code>""</code></td><td><code>assertEquals("", ...)</code></td></tr>
483
+ <tr><td><code>getLong()</code></td><td><code>0L</code></td><td><code>assertEquals(0L, ...)</code></td></tr>
484
+ <tr><td><code>getBigDecimal()</code></td><td><code>ZERO</code></td><td><code>assertEquals(ZERO, ...)</code></td></tr>
485
+ <tr><td><code>getBoolean()</code></td><td><code>false</code></td><td><code>assertFalse(...)</code></td></tr>
486
+ <tr><td><code>getDate()</code></td><td><strong>可能 null</strong></td><td><code>assertNull(...)</code></td></tr>
487
+ </tbody>
488
+ </table>
489
+ </div>
490
+ </div>
491
+ </div>
492
+
493
+ <!-- Step 3 测试场景 -->
494
+ <div class="full-width">
495
+ <p class="section-title">Step 3 — 测试场景设计</p>
496
+ <div class="grid-2">
497
+ <div class="card">
498
+ <h2>🎯 场景类型与边界值要求</h2>
499
+ <table>
500
+ <thead><tr><th>场景类型</th><th>说明</th></tr></thead>
501
+ <tbody>
502
+ <tr><td><span class="badge-green">正常路径</span></td><td>标准输入,预期正常输出</td></tr>
503
+ <tr><td><span class="badge-orange">边界条件</span></td><td>空值 / 空集合 / 零值</td></tr>
504
+ <tr><td><span class="badge-red">⭐ 边界值(重点)</span></td><td>比较运算符临界点,必须覆盖「刚好满足」和「刚好不满足」两侧</td></tr>
505
+ <tr><td><span class="badge-blue">异常路径</span></td><td>查询返回 null、抛异常</td></tr>
506
+ <tr><td><span class="badge-purple">业务规则</span></td><td>特定业务条件分支</td></tr>
507
+ </tbody>
508
+ </table>
509
+ <h3 style="margin-top:12px;">边界值覆盖要求</h3>
510
+ <table>
511
+ <thead><tr><th>运算符</th><th>必须覆盖的值</th></tr></thead>
512
+ <tbody>
513
+ <tr><td><code>&gt; 0</code></td><td>等于0(不通过)、大于0(通过)</td></tr>
514
+ <tr><td><code>&gt;= 0</code></td><td>等于0(通过)、小于0(不通过)</td></tr>
515
+ <tr><td><code>compareTo &gt; 0</code></td><td>a==b(不通过)、a&gt;b(通过)、a&lt;b(不通过)</td></tr>
516
+ <tr><td><code>size() &gt; N</code></td><td>size==N(不通过)、size==N+1(通过)</td></tr>
517
+ <tr><td><code>isEmpty()</code></td><td>空集合、恰好1条、多条</td></tr>
518
+ </tbody>
519
+ </table>
520
+ </div>
521
+ <div class="card">
522
+ <h2>🌿 switch / if-else 分支拆分策略</h2>
523
+ <table>
524
+ <thead><tr><th>分支类型</th><th>拆分规则</th></tr></thead>
525
+ <tbody>
526
+ <tr><td><strong>switch 分支</strong></td><td>每个 case <strong>必须</strong>定义为独立测试方法<br><code>testPropertyChanged_CaseFieldA()</code></td></tr>
527
+ <tr><td>switch case 内部多场景</td><td>允许在同一方法中测试(过复杂时可进一步拆分)</td></tr>
528
+ <tr><td><strong>if/else 判断</strong></td><td>覆盖所有条件组合(笛卡尔积),可合并到同一方法</td></tr>
529
+ <tr><td>关联方法的分支</td><td>被测方法调用的其他方法中的分支也需覆盖</td></tr>
530
+ </tbody>
531
+ </table>
532
+ <h3 style="margin-top:14px;">Step 4 — 验证与运行</h3>
533
+ <div class="rule-item">
534
+ <span class="rule-icon">✅</span>
535
+ <div class="rule-text"><strong>静态分析</strong> <span>import 完整 / MockedStatic 全部关闭 / 方法命名清晰 / 常量引用正确 / <strong style="color:#0366d6">BaseTest 未重复声明(v1.4)</strong> / <strong style="color:#0366d6">super.before() 在前(v1.4)</strong></span></div>
536
+ </div>
537
+ <div class="rule-item">
538
+ <span class="rule-icon">▶️</span>
539
+ <div class="rule-text"><strong>Gradle 运行前快速判断</strong> <span>— 先检查 ① biz lib 中被测模块 jar 是否存在 ② config.gradle 中 biz 路径是否真实存在;任一失败 → 直接跳过 Gradle,提示用户 IDE 运行(节省 ~8min)</span></div>
540
+ </div>
541
+ <div class="rule-item">
542
+ <span class="rule-icon">▶️</span>
543
+ <div class="rule-text"><strong>Gradle 运行</strong> <span>(将 <code>&lt;模块组&gt;</code> 替换为实际前缀)</span></div>
544
+ </div>
545
+ <pre style="margin-top:6px;">gradle :&lt;模块组&gt;-common:test
546
+ gradle :&lt;模块组&gt;-business:test
547
+ gradle :&lt;模块组&gt;-opplugin:test
548
+ gradle :&lt;模块组&gt;-formplugin:test
549
+ <span class="cmt"># 例如 mmc-sfc 模块:</span>
550
+ gradle :mmc-sfc-business:test</pre>
551
+ </div>
552
+ </div>
553
+ </div>
554
+
555
+ <!-- BaseTest 差异对比 -->
556
+ <div class="full-width">
557
+ <p class="section-title">BaseTest 差异对比</p>
558
+ <div class="grid-2">
559
+ <div class="card">
560
+ <h2>📋 通用 BaseTest 发现规则</h2>
561
+ <p style="font-size:12px;color:#586069;margin-bottom:10px;"><strong>实际使用时必须先读取目标工程的 BaseTest 源码</strong>,确认其预置了哪些 MockedStatic。</p>
562
+ <table>
563
+ <thead><tr><th>工程</th><th>BaseTest 包名规律</th><th>查找路径</th></tr></thead>
564
+ <tbody>
565
+ <tr><td><code>&lt;模块组&gt;-common</code></td><td><code>kd.&lt;包路径&gt;.common.BaseTest</code></td><td><code>&lt;模块组&gt;-common/src/test/.../BaseTest.java</code></td></tr>
566
+ <tr><td><code>&lt;模块组&gt;-business</code></td><td><code>kd.&lt;包路径&gt;.business.BaseTest</code></td><td><code>&lt;模块组&gt;-business/src/test/.../BaseTest.java</code></td></tr>
567
+ <tr><td><code>&lt;模块组&gt;-opplugin</code></td><td><code>kd.&lt;包路径&gt;.opplugin.BaseTest</code></td><td><code>&lt;模块组&gt;-opplugin/src/test/.../BaseTest.java</code></td></tr>
568
+ <tr><td><code>&lt;模块组&gt;-formplugin</code></td><td><code>kd.&lt;包路径&gt;.formplugin.BasePluginTest</code></td><td><code>&lt;模块组&gt;-formplugin/src/test/.../BasePluginTest.java</code></td></tr>
569
+ </tbody>
570
+ </table>
571
+ </div>
572
+ <div class="card">
573
+ <h2>🔖 mmc-sfc 模块已知预置 Mock(参考)</h2>
574
+ <p style="font-size:12px;color:#586069;margin-bottom:8px;">三个工程(common/business/opplugin)共同预置 8 个 MockedStatic:OrgUnitServiceHelper、BillTypeParamHelper、ResManager、SystemParamServiceHelper、QueryServiceHelper、BusinessDataServiceHelper、OperationServiceHelper、BaseDataServiceHelper。</p>
575
+ <table>
576
+ <thead><tr><th>工程</th><th>额外预置 Mock</th></tr></thead>
577
+ <tbody>
578
+ <tr><td>mmc-sfc-common</td><td>SaveServiceHelper + EntityMetadataCache</td></tr>
579
+ <tr><td>mmc-sfc-business</td><td>SaveServiceHelper + IDataModel/IFormView;BillUnitAndQtytHelper(仅声明未初始化)</td></tr>
580
+ <tr><td>mmc-sfc-opplugin</td><td>Singleton + BFTrackerServiceHelper + MutexUtils</td></tr>
581
+ <tr><td>mmc-sfc-formplugin</td><td>BasePluginTest&lt;T&gt; 独立管理,以实际文件为准</td></tr>
582
+ </tbody>
583
+ </table>
584
+ </div>
585
+ </div>
586
+ </div>
587
+
588
+ <!-- 常用 Mock 清单 -->
589
+ <div class="full-width">
590
+ <p class="section-title">常用 Mock 静态类清单</p>
591
+ <div class="card">
592
+ <table>
593
+ <thead><tr><th>静态类</th><th>用途</th><th>常见 Mock 方式</th><th>特殊要求</th></tr></thead>
594
+ <tbody>
595
+ <tr><td><code>QueryServiceHelper</code></td><td>数据库查询</td><td><code>.when(() -&gt; query(...)).thenReturn(...)</code></td><td>—</td></tr>
596
+ <tr><td><code>BusinessDataServiceHelper</code></td><td>业务数据加载</td><td><code>.when(() -&gt; loadSingle/load/loadFromCache(...)).thenReturn(...)</code></td><td>—</td></tr>
597
+ <tr><td><code>OperationServiceHelper</code></td><td>操作执行</td><td><code>.when(() -&gt; executeOperate(...)).thenReturn(opResult)</code></td><td>—</td></tr>
598
+ <tr><td><code>SaveServiceHelper</code></td><td>数据保存</td><td><code>.when(() -&gt; save/update(...)).thenReturn(...)</code></td><td>—</td></tr>
599
+ <tr><td><code>BaseDataServiceHelper</code></td><td>基础资料</td><td><code>.when(() -&gt; getBaseDataFromCache(...)).thenReturn(...)</code></td><td>—</td></tr>
600
+ <tr><td><code>ResManager</code></td><td>多语言资源</td><td><code>.thenAnswer(inv -&gt; inv.getArgument(0))</code></td><td>—</td></tr>
601
+ <tr><td><code>EntityMetadataCache</code></td><td>元数据缓存</td><td>直接 mockStatic</td><td>common 工程常用</td></tr>
602
+ <tr><td><code>CacheFactory</code></td><td>缓存工厂</td><td>Mock getCommonCacheFactory 链式调用</td><td>—</td></tr>
603
+ <tr><td><code>ConvertServiceHelper</code></td><td>BOTP 转换</td><td>Mock push/convert 方法</td><td>—</td></tr>
604
+ <tr><td><code>DB</code>(kd.bos.db.DB)</td><td>数据库操作</td><td>mockStatic</td><td style="color:#dc2626;font-weight:600;">必须先 ClassPool defrost(见 2.6)</td></tr>
605
+ <tr><td><code>ProcessSettleHelper</code></td><td>工序结算</td><td><code>ProcessSettleMockHelper.mockDistributeSessionlessCache()</code></td><td style="color:#dc2626;font-weight:600;">禁止直接 mockStatic(见 2.7)</td></tr>
606
+ </tbody>
607
+ </table>
608
+ </div>
609
+ </div>
610
+
611
+ <!-- 高频混淆场景 -->
612
+ <div class="full-width">
613
+ <p class="section-title">高频混淆场景速查</p>
614
+ <div class="card">
615
+ <table>
616
+ <thead><tr><th>场景</th><th class="x-mark">❌ 错误做法</th><th class="check-mark">✅ 正确做法</th></tr></thead>
617
+ <tbody>
618
+ <tr class="confusion-row-bad"><td>测试 protected 方法</td><td>改方法可见性为 public</td><td><code>ReflectHelper.invokeProtectedMethod()</code></td></tr>
619
+ <tr class="confusion-row-bad"><td>测试 private static 方法</td><td>跳过或改可见性</td><td><code>ReflectHelper.invokeStaticMethod()</code></td></tr>
620
+ <tr class="confusion-row-bad"><td>校验器收集错误</td><td>直接 validate 不收集</td><td>匿名子类重写 <code>addMessage/addErrorMessage</code> 收集到 List</td></tr>
621
+ <tr class="confusion-row-bad"><td>POJO 测试用 Mock</td><td>给纯 POJO 加 MockedStatic</td><td>直接 <code>new</code> 对象,无需任何 Mock</td></tr>
622
+ <tr class="confusion-row-bad"><td>断言写法</td><td><code>assertTrue(true)</code> 假断言 / 测试方法无任何 assert</td><td>断言真实输出或 <code>verify()</code> 交互;提前返回用 <code>verify(mock, never()).method()</code></td></tr>
623
+ <tr class="confusion-row-bad"><td>Mockito 重载歧义</td><td><code>any()</code> 无法区分重载</td><td>显式指定 <code>any(DynamicObject.class)</code></td></tr>
624
+ <tr class="confusion-row-bad"><td><strong>any() 类型歧义</strong></td><td><code>any()</code> 用于 Date/Set/String 等具体类型参数</td><td>必须明确类型:<code>any(Date.class)</code>、<code>any(Set.class)</code>、<code>anyString()</code>;<code>any()</code> 仅在参数类型为 Object 且无歧义时使用</td></tr>
625
+ <tr class="confusion-row-bad"><td>varargs 参数匹配</td><td><code>any(String[].class)</code></td><td><code>anyString()</code>(varargs 专用)</td></tr>
626
+ <tr class="confusion-row-bad"><td>DynamicObjectCollection 状态</td><td><code>new DynamicObjectCollection().add()</code> 后 isEmpty/size 不可靠</td><td><code>mock(DynamicObjectCollection.class)</code> + 显式 stub</td></tr>
627
+ <tr class="confusion-row-bad"><td>点分隔字段名</td><td><code>DynamicObjectMocker.add("a.b", v)</code> 后 <code>getLong("a.b")</code> 抛 ORMDesignException</td><td>平展查询结果用 <code>mock(DynamicObject.class)</code> + <code>when().thenReturn()</code></td></tr>
628
+ <tr class="confusion-row-bad"><td>Mock DB 类</td><td>直接 <code>mockStatic(DB.class)</code></td><td>先执行 ClassPool defrost(见 2.6)</td></tr>
629
+ <tr class="confusion-row-bad"><td>常量/枚举引用不到</td><td>用字面值 <code>"A"</code> 替代</td><td><strong>禁止字面值替代</strong>,主动告知用户补充</td></tr>
630
+ <tr class="confusion-row-bad"><td>DynamicObject 空值断言</td><td><code>assertNull(obj.getString("field"))</code></td><td><code>getString()</code>/<code>getLong()</code>/<code>getInt()</code>/<code>getBigDecimal()</code>/<code>getBoolean()</code> 平台默认不返回 null(即使 set null 也转为基本值):空字符串用 <code>assertEquals("", obj.getString(...))</code>、空数值用 <code>assertEquals(0L, obj.getLong(...))</code>、空 BigDecimal 用 <code>assertEquals(BigDecimal.ZERO, ...)</code>、布尔用 <code>assertFalse(obj.getBoolean(...))</code>。<br>若被测代码对 getter 结果做 <code>null</code> 检查(如 <code>if (obj.getBigDecimal("qty") != null)</code>),说明字段可能开启「允许为空」,测试必须覆盖 null 和非 null 两分支;无法判断时默认按非 null,在测试方法中加注释 <code>// 假设字段未开启"允许为空",若实际可为 null 请告知</code>。<br><code>getDate(key)</code> 是例外,始终可能为 null,用 <code>assertNull</code> 或具体日期值断言。</td></tr>
631
+ <tr class="confusion-row-bad"><td><strong>DynamicObjectMocker + .set()</strong></td><td>被测代码调 <code>dataEntity.set(field, v)</code>,dataEntity 由 DynamicObjectMocker 创建</td><td>改用 <code>mock(DynamicObject.class)</code> + stub,<code>.set()</code> 在 mock 对象上是空操作,不抛 ORMDesignException</td></tr>
632
+ </tbody>
633
+ </table>
634
+ </div>
635
+ </div>
636
+
637
+ <!-- 各模式快速参考 -->
638
+ <div class="full-width">
639
+ <p class="section-title">各测试模式快速参考</p>
640
+ <div class="grid-2">
641
+ <div class="card">
642
+ <h2>📦 common 模块 — POJO 模式</h2>
643
+ <pre><span class="kw">public class</span> <span class="cls">XxxModelTest</span> {
644
+ <span class="kw">private</span> <span class="cls">XxxModel</span> model;
645
+
646
+ <span class="ann">@Before</span>
647
+ <span class="kw">public void</span> setUp() { model = <span class="kw">new</span> <span class="cls">XxxModel</span>(); }
648
+
649
+ <span class="ann">@UnittestCaseInfo</span>(author = <span class="str">"..."</span>, ...)
650
+ <span class="ann">@Test</span>
651
+ <span class="kw">public void</span> testGetSetFieldName() {
652
+ model.setFieldName(<span class="str">"value"</span>);
653
+ assertEquals(<span class="str">"value"</span>, model.getFieldName());
654
+ }
655
+
656
+ <span class="ann">@Test</span>
657
+ <span class="kw">public void</span> testDefaultValues() {
658
+ <span class="cmt">// 输出字段默认为 ZERO,输入字段默认 null</span>
659
+ assertEquals(<span class="cls">BigDecimal</span>.ZERO, model.getAmount());
660
+ assertNull(model.getFormId());
661
+ }
662
+ }</pre>
663
+ </div>
664
+ <div class="card">
665
+ <h2>🏭 business 模块 — Helper 模式</h2>
666
+ <pre><span class="kw">public class</span> <span class="cls">XxxHelperTest</span>
667
+ <span class="kw">extends</span> <span class="cls">AbstractJunitNoDependenciesResManagerTest</span> {
668
+
669
+ <span class="cls">MockedStatic</span>&lt;<span class="cls">QueryServiceHelper</span>&gt; qsh;
670
+
671
+ <span class="ann">@Before</span> <span class="kw">public void</span> before() {
672
+ qsh = mockStatic(<span class="cls">QueryServiceHelper</span>.class);
673
+ }
674
+ <span class="ann">@After</span> <span class="kw">public void</span> after() { qsh.close(); }
675
+
676
+ <span class="ann">@UnittestCaseInfo</span>(...)
677
+ <span class="ann">@Test</span>
678
+ <span class="ann">@DisplayName</span>(<span class="str">"正常路径"</span>)
679
+ <span class="kw">public void</span> testMethod_NormalCase() {
680
+ qsh.when(() -> <span class="cls">QueryServiceHelper</span>.query(...))
681
+ .thenReturn(mockData);
682
+ <span class="cls">Object</span> result = <span class="cls">XxxHelper</span>.method(param);
683
+ assertNotNull(result);
684
+ reset();
685
+ }
686
+ }</pre>
687
+ </div>
688
+ </div>
689
+ <div class="grid-2" style="margin-top:20px;">
690
+ <div class="card">
691
+ <h2>🔒 opplugin — Validator 模式</h2>
692
+ <pre><span class="kw">public class</span> <span class="cls">XxxValidatorTest</span>
693
+ <span class="kw">extends</span> <span class="cls">AbstractJunitNoDependenciesResManagerTest</span> {
694
+
695
+ <span class="kw">private final</span> List&lt;String&gt; errors = <span class="kw">new</span> ArrayList&lt;&gt;();
696
+
697
+ <span class="kw">private</span> <span class="cls">XxxValidator</span> buildValidator() {
698
+ <span class="kw">return new</span> <span class="cls">XxxValidator</span>() {
699
+ <span class="ann">@Override</span>
700
+ <span class="kw">protected void</span> addErrorMessage(
701
+ <span class="cls">ExtendedDataEntity</span> e, <span class="cls">String</span> msg) {
702
+ errors.add(msg);
703
+ }
704
+ };
705
+ }
706
+
707
+ <span class="ann">@Test</span>
708
+ <span class="kw">public void</span> testValidate_ShouldPass() {
709
+ <span class="cls">XxxValidator</span> v = buildValidator();
710
+ v.setDataEntities(buildData());
711
+ v.validate();
712
+ assertEquals(<span class="num">0</span>, errors.size());
713
+ }
714
+ }</pre>
715
+ </div>
716
+ <div class="card">
717
+ <h2>🖥️ formplugin — FormPlugin 模式</h2>
718
+ <pre><span class="kw">public class</span> <span class="cls">XxxPluginTest</span>
719
+ <span class="kw">extends</span> <span class="cls">BasePluginTest</span>&lt;<span class="cls">XxxPlugin</span>&gt; {
720
+
721
+ <span class="kw">private</span> <span class="cls">MockedStatic</span>&lt;<span class="cls">SomeHelper</span>&gt; someHelper;
722
+
723
+ <span class="ann">@Before</span> <span class="kw">public void</span> before() {
724
+ someHelper = mockStatic(<span class="cls">SomeHelper</span>.class);
725
+ }
726
+ <span class="ann">@After</span> <span class="kw">public void</span> after() {
727
+ someHelper.close();
728
+ }
729
+
730
+ <span class="cmt">// switch 每个 case 独立方法</span>
731
+ <span class="ann">@Test</span>
732
+ <span class="kw">public void</span> testPropertyChanged_CaseMaterialId() { ... }
733
+ <span class="ann">@Test</span>
734
+ <span class="kw">public void</span> testPropertyChanged_CaseQty() { ... }
735
+ }</pre>
736
+ </div>
737
+ </div>
738
+ </div>
739
+
740
+ <div style="text-align:center;padding:20px 0 10px;color:#9ca3af;font-size:12px;">
741
+ cosmic-unittest v1.4 &nbsp;·&nbsp; 适用于 common / business / opplugin / formplugin 四工程结构的苍穹模块 &nbsp;·&nbsp; 6 项性能优化
742
+ </div>
743
+
744
+ </div>
745
+ </body>
746
+ </html>