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,788 @@
1
+ ---
2
+ name: cosmic-unittest
3
+ version: "1.4"
4
+ description: |
5
+ 苍穹模块单元测试编写技能 — 适用于任何遵循 common / business / opplugin / formplugin 四工程结构的苍穹模块。
6
+ 当用户说"写单测"、"写单元测试"、"写测试"、"写测试用例"、"补单测"、"补测试"、"加测试"、"生成测试"、
7
+ "unittest"、"unit test"、"test case",或打开苍穹模块的 Java 源码/测试文件并要求编写测试时触发。
8
+ globs:
9
+ # 源码文件(用户打开被测类时触发)
10
+ - "**/*-common/src/main/**/*.java"
11
+ - "**/*-business/src/main/**/*.java"
12
+ - "**/*-opplugin/src/main/**/*.java"
13
+ - "**/*-formplugin/src/main/**/*.java"
14
+ # 测试文件(用户打开或编辑已有测试时触发)
15
+ - "**/*-common/src/test/**/*.java"
16
+ - "**/*-business/src/test/**/*.java"
17
+ - "**/*-opplugin/src/test/**/*.java"
18
+ - "**/*-formplugin/src/test/**/*.java"
19
+ ---
20
+
21
+ # 苍穹模块单元测试编写技能
22
+
23
+ ## 技能说明
24
+
25
+ 本技能适用于任何遵循「四工程结构」的金蝶云苍穹模块,包括但不限于:
26
+ - **mmc-sfc**(SFC 车间管理)、**bd-mpdm**(生产数据管理)、**sys-xkbase** 等各业务模块
27
+
28
+ 四大工程角色通用定义:
29
+ - **`<模块组>-common`**:公共模块(常量类 / 枚举类 / POJO / 工具类)
30
+ - **`<模块组>-business`**:静态帮助类 / 业务逻辑
31
+ - **`<模块组>-opplugin`**:操作插件(Validator / OpPlugin / ConvertPlugin)
32
+ - **`<模块组>-formplugin`**:表单插件(继承 AbstractFormPlugin / AbstractListPlugin / AbstractBillPlugin 的子类)
33
+
34
+ > `<模块组>` 是被测类所在模块的前缀,需在 Step 0 中自动识别(如 `mmc-sfc`、`bd-mpdm`)。
35
+
36
+ ---
37
+
38
+ ## 目录结构
39
+
40
+ ```
41
+ cosmic-unittest/
42
+ ├── SKILL.md ← 本文件:技能入口与决策树
43
+ ├── patterns/
44
+ │ ├── common-module.md ← 公共模块测试模式(POJO / 枚举 / 工具类)
45
+ │ ├── business-helper.md ← 业务帮助类测试模式
46
+ │ ├── validator.md ← 校验器测试模式
47
+ │ ├── op-plugin.md ← 操作插件测试模式
48
+ │ ├── convert-plugin.md ← 转换插件测试模式
49
+ │ └── formplugin.md ← 表单插件测试模式(formplugin 工程专用)
50
+ └── examples/
51
+ ├── common-test.md ← 完整 common 测试示例(POJO / 枚举 / 工具类)
52
+ ├── business-test.md ← 完整 business 测试示例
53
+ ├── validator-test.md ← 完整 validator 测试示例
54
+ ├── op-plugin-test.md ← 完整 op-plugin 测试示例
55
+ └── formplugin-test.md ← 完整 formplugin 测试示例(BasePluginTest<T> 继承模式)
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Step 0: 判断测试类型
61
+
62
+ ### Step 0.0: 识别模块组前缀
63
+
64
+ **首先**从被测类的包名或文件路径中提取**模块组前缀**:
65
+
66
+ ```
67
+ 包名示例 对应模块组前缀
68
+ kd.mmc.sfc.business.XxxHelper → mmc-sfc
69
+ kd.bd.mpdm.business.XxxHelper → bd-mpdm
70
+ kd.sys.xkbase.business.XxxHelper → sys-xkbase
71
+ ```
72
+
73
+ 识别后将前缀统一称为 `<模块组>`,后续所有工程名、包名、路径均基于此进行推导。
74
+
75
+ ### Step 0.1: 根据工程类型决定测试模式
76
+
77
+ ### ⚡ 按需加载 pattern 文件(性能优化)
78
+
79
+ 根据 Step 0.1 识别出的工程类型,**只读取对应的 1 个 pattern 文件**,禁止全量加载所有 pattern:
80
+
81
+ | 工程类型 | 读取的 pattern 文件 | 按需参考的 example 文件 |
82
+ |---------|-------------------|---------------------|
83
+ | common - POJO/DTO | `patterns/common-module.md` #POJO 段 | `examples/common-test.md` POJO 段 |
84
+ | common - 枚举 | `patterns/common-module.md` #Enum 段 | `examples/common-test.md` Enum 段 |
85
+ | common - 工具类 | `patterns/common-module.md` #Utils 段 | `examples/common-test.md` Utils 段 |
86
+ | business | `patterns/business-helper.md` | `examples/business-test.md` |
87
+ | opplugin - Validator | `patterns/validator.md` | `examples/validator-test.md` |
88
+ | opplugin - OpPlugin | `patterns/op-plugin.md` | `examples/op-plugin-test.md` |
89
+ | opplugin - ConvertPlugin | `patterns/convert-plugin.md` | `examples/op-plugin-test.md`(参考结构) |
90
+ | formplugin | `patterns/formplugin.md` | `examples/formplugin-test.md` |
91
+
92
+ > example 文件仅在需要参考具体写法时读取,非必读。
93
+
94
+ ### Step 0.2: 批量测试识别(同工程多个类)
95
+
96
+ 若用户一次提交了**同一工程下多个被测类**,采用批量模式提升效率:
97
+
98
+ ```
99
+ 批量模式流程
100
+
101
+ ├─ Step 1:并行读取所有被测类源码,统一完成分析(含 Bug 检查)
102
+ ├─ Step 2:跳过分析摘要确认(遵循用户偏好),直接生成代码
103
+ ├─ Step 3:依次生成所有测试类代码(可并行生成互不依赖的测试类)
104
+ └─ Step 4:全部生成完成后,执行一次 Gradle 运行,统一验证
105
+ ```
106
+
107
+ > 批量模式的核心价值:① **Gradle 只冷启动一次**;② **文件读取并行化**(所有被测类源码同一轮并行读取);③ **测试类生成可并行**(同工程不同被测类的测试类互不依赖)。
108
+
109
+ ### ⚡ 批量模式并行策略(性能优化)
110
+
111
+ 批量模式下,各步骤的并行执行策略:
112
+
113
+ | 步骤 | 并行策略 | 说明 |
114
+ |------|---------|------|
115
+ | 源码读取 | 所有被测类 + 所有常量类 + BaseTest 同一轮并行读取 | 减少多轮串行等待 |
116
+ | 逻辑分析 | 逐类分析,但汇总输出一次 | 批量模式仍跳过确认环节 |
117
+ | 代码生成 | 互不依赖的测试类可并行生成 | 同工程的 BaseTest 共享,减少重复读取 |
118
+ | Gradle 验证 | 只执行一次 `gradle :<模块组>-<工程>:test` | 所有测试类合并一次运行 |
119
+
120
+ 收到用户「写单元测试」请求时,根据被测类所在工程决定测试模式:
121
+
122
+ ```
123
+ 被测类在哪个工程?
124
+
125
+ ├─ <模块组>-common(公共模块:常量 / 枚举 / POJO / 工具类)
126
+ │ ├─ POJO / DTO / Bean 类(纯 getter/setter)
127
+ │ │ → 使用 POJO 测试模式(读 patterns/common-module.md #POJO)
128
+ │ │ → 无需 Mock,无需基类,直接 new 对象测试
129
+ │ │
130
+ │ ├─ 枚举类(含业务方法如 getByCode / match)
131
+ │ │ → 使用 Enum 测试模式(读 patterns/common-module.md #Enum)
132
+ │ │ → 继承 AbstractJunitNoDependenciesTest
133
+ │ │
134
+ │ └─ 工具类(XxxUtil / XxxUtils,含静态方法)
135
+ │ → 使用 Common Utils 测试模式(读 patterns/common-module.md #Utils)
136
+ │ → 继承当前模块 common 工程的 BaseTest 或 AbstractJunitNoDependenciesResManagerTest
137
+ │ → 测试文件放在: <模块组>-common/src/test/java/...
138
+
139
+ ├─ <模块组>-business(XxxHelper / XxxService 等静态工具类)
140
+ │ → 使用 Business Helper 测试模式(读 patterns/business-helper.md)
141
+ │ → 测试文件放在: <模块组>-business/src/test/java/...
142
+
143
+ ├─ <模块组>-opplugin
144
+ │ ├─ Validator 目录下的校验器类
145
+ │ │ → 使用 Validator 测试模式(读 patterns/validator.md)
146
+ │ │
147
+ │ ├─ 继承 AbstractOperationServicePlugIn 的操作插件
148
+ │ │ → 使用 OpPlugin 测试模式(读 patterns/op-plugin.md)
149
+ │ │
150
+ │ └─ ConvertPlugin / BotpPlugin 等转换插件
151
+ │ → 使用 ConvertPlugin 测试模式(读 patterns/convert-plugin.md)
152
+ │ → 测试文件放在: <模块组>-opplugin/src/test/java/...
153
+
154
+ └─ <模块组>-formplugin(表单插件,继承 AbstractFormPlugin / AbstractListPlugin / AbstractBillPlugin 的子类)
155
+ → 使用 FormPlugin 测试模式(读 patterns/formplugin.md)
156
+ → 基类与参数获取优先级:
157
+ 1. 优先在 <模块组>-formplugin/src/test/java/ 下搜索 BasePluginTest 获取测试所需参数及 mock 取值逻辑
158
+ 2. 若 BasePluginTest 中未定义相关逻辑,参考同包及子包下 *Test 结尾类的类似写法
159
+ 3. 若以上均无可参考案例,尝试使用苍穹公共单测框架,并**主动告知用户**缺失的取值逻辑,由用户补充到 BasePluginTest
160
+ → 测试文件放在: <模块组>-formplugin/src/test/java/... (与被测类保持相同包路径)
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Step 0.5: 获取 Author 信息
166
+
167
+ 在生成 `@UnittestCaseInfo` 注解前,按以下优先级获取作者信息:
168
+
169
+ ### 优先级 1:从缓存文件读取(快速路径)
170
+
171
+ 尝试读取 `.qoder/skills/cosmic-unittest/author-cache.json`:
172
+
173
+ ```json
174
+ {
175
+ "author": "zhang_san <zhang_san@kingdee.com>",
176
+ "lastUpdateAuthor": "zhang_san <zhang_san@kingdee.com>"
177
+ }
178
+ ```
179
+
180
+ 若文件存在且 `author` 字段非空,**直接使用缓存值,跳过 git config 命令**。
181
+
182
+ ### 优先级 2:从 git config 读取并写入缓存
183
+
184
+ 若缓存文件不存在或 `author` 为空,执行:
185
+
186
+ ```bash
187
+ git config user.name
188
+ git config user.email
189
+ ```
190
+
191
+ 读取成功后:
192
+ 1. 按格式拼接:`"<name> <<name>@kingdee.com>"`,例如 `"zhang_san <zhang_san@kingdee.com>"`
193
+ 2. **将结果写入 `.qoder/skills/cosmic-unittest/author-cache.json`**,供后续直接复用
194
+
195
+ ### 优先级 3:询问用户
196
+
197
+ 若 git config 读取失败,主动询问用户提供姓名和邮箱,填写后同样写入缓存文件。
198
+
199
+ ### .gitignore 检查
200
+
201
+ 首次写入 `author-cache.json` 后,检查项目根目录的 `.gitignore` 是否已包含该文件的忽略规则:
202
+
203
+ ```
204
+ .qoder/skills/cosmic-unittest/author-cache.json
205
+ ```
206
+
207
+ 若未忽略,**提示用户将上述规则添加到 `.gitignore`**,避免个人信息被提交到 git 仓库。
208
+
209
+ ---
210
+
211
+ > `lastUpdateTime` = 用例编写时的当前时间,格式 `yyyy-MM-dd HH:mm:ss`
212
+
213
+ ---
214
+
215
+ ## Step 1: 分析被测类
216
+
217
+ ### 快速模式判断(POJO / 枚举 → 跳过分析摘要)
218
+
219
+ 若被测类属于以下**极简场景**,**直接跳过「被测逻辑分析」摘要输出,立即生成测试代码**:
220
+
221
+ | 极简场景 | 判断依据 |
222
+ |---------|--------|
223
+ | **POJO / DTO / Bean** | 类中只有字段 + getter/setter/构造方法,无业务逻辑方法 |
224
+ | **纯枚举类** | 仅含枚举值定义,无自定义业务方法(如 `getByCode` / `match`);或只有一个简单的 `getByCode` 静态方法 |
225
+
226
+ > 对于上述场景,无需 Mock、无分支、无依赖,分析摘要价值极低,直接生成代码效率更高。
227
+
228
+ ---
229
+
230
+ 其余类型(工具类 / Business Helper / Validator / OpPlugin / FormPlugin)按完整流程执行:
231
+
232
+ 1. 读取被测类的完整源码
233
+ 2. 识别所有 public / protected 方法
234
+ 3. 识别被测方法中调用的**静态方法**(这些需要 MockedStatic)
235
+ 4. 识别被测方法中依赖的**外部服务**(QueryServiceHelper、BusinessDataServiceHelper 等)
236
+ 5. 识别方法参数中的 **DynamicObject** 结构(需要 DynamicObjectMocker 构造)
237
+ 6. **识别被测方法中的 switch 分支和判断分支**(用于 Step 3 设计测试方法拆分策略)
238
+ 7. **检查被测代码是否存在 Bug**(见下方说明)
239
+
240
+ ### ⚡ 并行全量读取(性能优化)
241
+
242
+ 读取被测类源码后,**立即将以下所有文件读取并行发起**,在同一轮 tool calls 中一次性拿齐,不得串行逐个等待:
243
+
244
+ | 读取目标 | 搜索/读取方式 | 说明 |
245
+ |---------|-------------|------|
246
+ | 常量类 / 枚举类 | 从 import 提取 `Const/Consts/Enum` 结尾的类名,grep_code 并行搜索 | 搜索实际使用的字段值 |
247
+ | BaseTest 源码 | search_file 搜索 `BaseTest.java` 或 `BasePluginTest.java` | 确认预置 MockedStatic |
248
+ | 对应 pattern 文件 | 直接 read_file(Step 0.1 已确定文件名) | 若 Step 0 未读取则在此补读 |
249
+ | 被测方法调用的关联类 | 从 import 提取同模块的 Helper/Service 类 | 读取方法签名 |
250
+
251
+ ```
252
+ 示例(一轮并行 tool calls):
253
+ 1. grep_code → ProcessPlanConsts 中被测方法使用的字段
254
+ 2. grep_code → SendWorkConsts 中被测方法使用的字段
255
+ 3. search_file → BaseTest.java
256
+ 4. read_file → patterns/op-plugin.md
257
+ 5. grep_code → BillUnitAndQtytHelper 方法签名
258
+ ```
259
+
260
+ > 本次优化来源:v1.3 已实现常量并行读取,v1.4 将其扩展为「全量并行」,
261
+ > 将 BaseTest、pattern、关联类的读取也纳入同一批次,减少多轮串行等待。
262
+
263
+ ### ⚡ 智能 Mock 推断(性能优化)
264
+
265
+ 从被测类的 import 语句中,**自动推断**需要 Mock 的静态类,与「常用 Mock 清单」对照匹配:
266
+
267
+ ```
268
+ 推断规则:
269
+ 1. 扫描被测类所有 import,提取「常用 Mock 清单」中出现的类
270
+ 2. 将这些类标记为「候选 Mock 类」
271
+ 3. 结合 BaseTest 已预置的 MockedStatic 进行去重(详见优化4)
272
+ 4. 仅对「候选 Mock 类 - BaseTest 已预置」的差集声明新的 MockedStatic
273
+ ```
274
+
275
+ **常见 import → Mock 映射表**:
276
+
277
+ | import 中的类 | 需 Mock 的方式 |
278
+ |-------------|-------------|
279
+ | `kd.bos.servicehelper.QueryServiceHelper` | `mockStatic` + `.when(() -> query(...))` |
280
+ | `kd.bos.servicehelper.BusinessDataServiceHelper` | `mockStatic` + `.when(() -> loadSingle/load/loadFromCache(...))` |
281
+ | `kd.bos.servicehelper.OperationServiceHelper` | `mockStatic` + `.when(() -> executeOperate(...))` |
282
+ | `kd.bos.servicehelper.SaveServiceHelper` | `mockStatic` + `.when(() -> save/update(...))` |
283
+ | `kd.bos.servicehelper.base.BaseDataServiceHelper` | `mockStatic` + `.when(() -> getBaseDataFromCache(...))` |
284
+ | `kd.bos.org.service.OrgUnitServiceHelper` | `mockStatic`(通常无需 stub 方法) |
285
+ | `kd.bos.servicehelper.SystemParamServiceHelper` | `mockStatic` + `.when(() -> getParameterValue(...))` |
286
+ | `kd.bos.resmanager.ResourceManager` | `mockStatic` + `.when(() -> loadKDString(...)).thenAnswer(inv -> inv.getArgument(0))` |
287
+ | `kd.bos.servicehelper.BillTypeParamHelper` | `mockStatic`(通常无需 stub 方法) |
288
+ | `kd.bos.db.DB` | `mockStatic`(**必须**先 ClassPool defrost,详见 2.6 节) |
289
+ | `kd.mmc.sfc.helper.ProcessSettleHelper` | **禁止**直接 mockStatic,必须用 `ProcessSettleMockHelper`(详见 2.7 节) |
290
+ | `kd.bos.cache.CacheFactory` | `mockStatic` + 链式 stub |
291
+
292
+ > 该映射表替代人工逐个判断「这个类需不需要 Mock」,将依赖识别从经验驱动变为规则驱动。
293
+
294
+ ### Bug 检查要求
295
+
296
+ 在阅读源码阶段,必须对被测代码进行 Bug 审查。**发现 Bug 时不得让测试用例「绕过」或「迁就」该 Bug 使测试通过**,而应:
297
+
298
+ 1. **让测试暴露 Bug**:断言应反映**正确的预期行为**,而非当前有缺陷的实际行为,使测试失败以暴露 Bug
299
+ 2. **在测试方法上方标注**:用注释 `// ⚠️ BUG: <说明>` 标注该测试预期失败的原因
300
+ 3. **同步告知用户**:生成测试完成后,在回复中明确列出发现的 Bug,说明位置和原因
301
+
302
+ **常见 Bug 类型:**
303
+
304
+ | Bug 类型 | 示例 | 应对方式 |
305
+ |---------|------|----------|
306
+ | 边界判断方向错误 | `> 0` 应为 `>= 0` | 测试临界值,断言正确预期结果(测试会失败,暴露 Bug) |
307
+ | 空指针风险 | 未判 null 直接调用方法 | 构造 null 入参,断言不抛异常 |
308
+ | 逻辑取反错误 | `if (!condition)` 逻辑反了 | 断言正确分支输出 |
309
+ | 数值计算错误 | 加减乘除逻辑有误 | 用具体数值断言期望的正确计算结果 |
310
+ | 集合判断遗漏 | 未判空直接取第一个元素 | 构造空集合,断言不抛异常 |
311
+
312
+ ### 分析结果输出要求
313
+
314
+ **完成 Step 1 分析后,必须在回复中输出以下结构化摘要,并等待用户确认后再进入 Step 2:**
315
+
316
+ ```
317
+ ## 被测逻辑分析
318
+
319
+ **被测类**:全限定类名
320
+ **被测方法**:方法签名列表
321
+
322
+ ### 核心逻辑梳理
323
+ - 列出每个被测方法的主要分支和执行路径
324
+
325
+ ### 依赖识别
326
+ - 静态方法(需 MockedStatic):列出类名.方法名
327
+ - 外部服务(需 Mock):列出类名
328
+ - DynamicObject 字段:列出需构造的字段及类型
329
+
330
+ ### Bug 检查结果
331
+ - ✅ 未发现 Bug / ⚠️ 发现 Bug(详细说明位置和原因)
332
+ ```
333
+
334
+ 输出摘要后,**必须追加以下提示,等待用户反馈**:
335
+
336
+ > 以上为分析结果,如有以下情况请现在告知,否则回复「继续」即可开始生成代码:
337
+ > - Mock 依赖识别有误(如遗漏了某个 Helper、或识别了不需要 Mock 的类)
338
+ > - 分支覆盖策略需要调整
339
+ > - 有需要特殊处理的字段或业务规则
340
+
341
+ > 不得跳过此输出直接给出代码。即使逻辑简单,也必须输出“未发现 Bug”的结论。
342
+
343
+ ---
344
+
345
+ ## Step 2: 编写测试类
346
+
347
+ ### 2.1 通用规则
348
+
349
+ | 规则 | 说明 |
350
+ |------|------|
351
+ | 测试框架 | JUnit 4(@Test / @Before / @After) |
352
+ | Mock 框架 | Mockito 2.x + MockedStatic |
353
+ | **Mockito 静态导入** | **禁止**使用 `import static org.mockito.ArgumentMatchers.*` 和 `import static org.mockito.Mockito.*` 通配符导入;必须显式逐个导入,标准模板:`any` / `anyBoolean` / `anyInt` / `anyLong` / `anyString` / `eq` / `isNull`(ArgumentMatchers),`atLeastOnce` / `mock` / `never` / `verify` / `when`(Mockito),按需单独添加其他方法 |
354
+ | 数据构造 | `DynamicObjectMocker`(链式 `.add(key, value)` 构造 DynamicObject) |
355
+ | 基类选择 | 无 ResManager 依赖 → `AbstractJunitNoDependenciesTest`;有 ResManager → `AbstractJunitNoDependenciesResManagerTest` |
356
+ | 反射工具 | `ReflectHelper.invokeProtectedMethod()` 测试 protected 方法;`ReflectHelper.invokeStaticMethod()` 测试 private static 方法 |
357
+ | 资源释放 | 所有 MockedStatic 必须在 `@After` 中 `.close()` |
358
+ | 注解 | 每个 @Test 方法**必须**加 `@UnittestCaseInfo` 和 `@DisplayName` |
359
+ | 断言质量 | **禁止假断言,禁止缺失断言**:① 不允许出现 `assertTrue(true)`、`assertFalse(false)`、`assertEquals(x, x)` 等永真/永假断言;② **每个 @Test 方法必须至少包含一条有效的 `assert` 或 `verify` 调用**,不允许测试方法中无任何断言(包括注释掉断言、只有 step 没有 assert 等)。每个断言必须验证被测方法的**实际输出或副作用**(如返回值、Mock 交互 `verify()`、抛出异常、DynamicObject 字段值变化等)。对于「提前返回」场景,使用 `verify(mock, never()).method()` 断言某个下游调用未被触发 |
360
+ | DynamicObject 断言 | 对 DynamicObject 取值断言时,除 `getDate(...)` 外(可能为 null),`getString()`、`getBigDecimal()`、`getLong()` 等默认不会为 null,使用基本值判定:空字符串用 `assertEquals("", obj.getString(...))`、空数值用 `assertEquals(0L, obj.getLong(...))`、空 BigDecimal 用 `assertEquals(BigDecimal.ZERO, obj.getBigDecimal(...))` |
361
+ | switch 分支策略 | 被测方法中的 **switch 分支**,每个 case 必须拆为**独立的测试方法**;case 内部的多个场景允许在同一方法中测试(过于复杂时可进一步拆分) |
362
+ | 判断分支覆盖 | 被测方法(含关联调用的其他方法)中的 **if/else 判断分支**,需尽量覆盖所有条件组合(笛卡尔积),可将不同条件取值合并到同一个测试方法中 |
363
+ | 常量/枚举引用 | 若引用的常量类、枚举类**无法找到**,必须**主动告知用户**,由用户补充文件位置或源码。**禁止**忽略使用逻辑或用字面值替代 |
364
+
365
+ ### ⚡ BaseTest Mock 继承去重(性能优化)
366
+
367
+ 测试类继承 BaseTest 后,BaseTest 已预置的 MockedStatic **不得重复声明**。按以下规则去重:
368
+
369
+ ```
370
+ 去重流程:
371
+ 1. 读取 BaseTest 源码,提取所有 @Before 中初始化的 MockedStatic 字段
372
+ 2. 在「智能 Mock 推断」产出的「候选 Mock 类」中,减去 BaseTest 已预置的类
373
+ 3. 测试类仅需声明「差集」对应的 MockedStatic 成员变量
374
+ 4. 在 @Before 中仅初始化差集的 MockedStatic
375
+ 5. 在 @After 中仅关闭差集的 MockedStatic
376
+ ```
377
+
378
+ **典型场景示例**(business 工程):
379
+
380
+ ```java
381
+ // BaseTest 已预置:QueryServiceHelper、BusinessDataServiceHelper 等 8+2 个
382
+ // 被测类还需要:ProcessPlanHelper(不在 BaseTest 中)
383
+
384
+ public class SomeHelperTest extends BaseTest {
385
+ // 仅声明 BaseTest 未预置的
386
+ MockedStatic<ProcessPlanHelper> processPlanHelper;
387
+
388
+ @Before
389
+ public void before() {
390
+ super.before(); // 触发 BaseTest 的预置 Mock
391
+ processPlanHelper = Mockito.mockStatic(ProcessPlanHelper.class);
392
+ }
393
+
394
+ @After
395
+ public void after() {
396
+ processPlanHelper.close();
397
+ super.after(); // BaseTest 关闭其预置 Mock
398
+ }
399
+ }
400
+ ```
401
+
402
+ > 去重的好处:① 减少 MockedStatic 声明和初始化代码量;② 避免 BaseTest 与测试类对同一类重复 mock 导致冲突;
403
+ > ③ 测试类代码更简洁,关注点集中在业务特有的 Mock 上。
404
+
405
+ ### 2.2 @UnittestCaseInfo 注解规范
406
+
407
+ ```java
408
+ @UnittestCaseInfo(
409
+ author = "<name> <<name>@kingdee.com>", // 格式:git config user.name 拼接邮箱,如 "zhang_san <zhang_san@kingdee.com>"
410
+ title = "英文标题(简短描述测试目的)",
411
+ targetClass = "被测类全限定名",
412
+ targetMethod = "被测方法名(小写)",
413
+ lastUpdateTime = "yyyy-MM-dd HH:mm:ss", // 用例编写时的当前时间
414
+ lastUpdateAuthor = "<name> <<name>@kingdee.com>", // 格式同 author
415
+ methodSignature = "被测方法完整签名",
416
+ testPoints = {"Functionality"},
417
+ description = "英文描述"
418
+ )
419
+ ```
420
+
421
+ ### 2.3 测试方法注释规范
422
+
423
+ ```java
424
+ @Test
425
+ @DisplayName("中文描述:测试场景说明")
426
+ public void testMethodName_ScenarioDescription() {
427
+ //step 准备测试数据 / 构建 Mock
428
+ ...
429
+ //step 执行被测方法
430
+ ...
431
+ //assert 断言结果
432
+ ...
433
+ reset(); // 如有 reset 方法
434
+ }
435
+ ```
436
+
437
+ ### 2.4 MockedStatic 生命周期
438
+
439
+ ```java
440
+ // 声明为成员变量
441
+ MockedStatic<XxxServiceHelper> xxxServiceHelper;
442
+
443
+ @Before
444
+ public void before() {
445
+ xxxServiceHelper = Mockito.mockStatic(XxxServiceHelper.class);
446
+ // 配置通用返回值...
447
+ }
448
+
449
+ @After
450
+ public void after() {
451
+ xxxServiceHelper.close(); // 必须关闭!
452
+ }
453
+
454
+ public void reset() {
455
+ xxxServiceHelper.reset(); // 在测试方法末尾重置状态
456
+ }
457
+ ```
458
+
459
+ ### 2.5 DynamicObjectMocker 用法
460
+
461
+ ```java
462
+ // 简单对象
463
+ DynamicObject obj = new DynamicObjectMocker()
464
+ .add("id", 1L)
465
+ .add("billno", "TEST-001")
466
+ .add("status", "A")
467
+ .getObject();
468
+
469
+ // 嵌套对象(基础资料引用)
470
+ DynamicObject obj = new DynamicObjectMocker()
471
+ .add("material", new DynamicObjectMocker()
472
+ .add("id", 100L)
473
+ .add("masterid.id", 100L)
474
+ .getObject())
475
+ .add("unit", new DynamicObjectMocker()
476
+ .add("id", 1L)
477
+ .add("precision", 2)
478
+ .getObject())
479
+ .getObject();
480
+
481
+ // 分录集合
482
+ DynamicObject bill = new DynamicObjectMocker()
483
+ .add("id", 1L)
484
+ .add("entryentity", new DynamicObjectMocker()
485
+ .add("id", 10L)
486
+ .add("seq", 1)
487
+ .add("qty", BigDecimal.TEN)
488
+ .getCollection()) // 注意:分录用 getCollection()
489
+ .getObject();
490
+
491
+ // 多行分录(用于遍历/stream 的场景)
492
+ DynamicObjectCollection entries = new DynamicObjectCollection();
493
+ entries.add(new DynamicObjectMocker().add("id", 1L).add("qty", BigDecimal.ONE).getObject());
494
+ entries.add(new DynamicObjectMocker().add("id", 2L).add("qty", BigDecimal.TEN).getObject());
495
+ DynamicObject bill = new DynamicObjectMocker()
496
+ .add("entryentity", entries)
497
+ .getObject();
498
+
499
+ // ⚠️ 当被测代码用 isEmpty()/size() 作为分支判断时,必须 mock 集合:
500
+ // new DynamicObjectCollection().add() 不能保证 isEmpty()/size() 返回正确值
501
+ DynamicObjectCollection entries = mock(DynamicObjectCollection.class);
502
+ when(entries.isEmpty()).thenReturn(false);
503
+ when(entries.size()).thenReturn(2);
504
+ ```
505
+
506
+ ### 2.6 kd.bos.db.DB 类特殊 Mock
507
+
508
+ 由于 `kd.bos.db.DB` 类的特殊性(字节码冻结问题),对该类的静态方法进行 mock 前,**必须**先执行 ClassPool defrost 处理:
509
+
510
+ ```java
511
+ public class SomeClassTest {
512
+
513
+ // 定义变量
514
+ MockedStatic<DB> dbMocked = Mockito.mockStatic(DB.class);
515
+
516
+ @Before
517
+ public void before() {
518
+ // 对 DB.class 可能已存在的 mock 执行释放
519
+ try {
520
+ CtClass cls = ClassPool.getDefault().get(DB.class.getName());
521
+ if (cls.isFrozen()) {
522
+ cls.defrost();
523
+ }
524
+ } catch (Exception e) {}
525
+ // 赋值新的 mock
526
+ dbMocked = Mockito.mockStatic(DB.class);
527
+ }
528
+
529
+ @After
530
+ public void after() {
531
+ dbMocked.close();
532
+ }
533
+ }
534
+ ```
535
+
536
+ 此模式遵循 2.4 节的 MockedStatic 生命周期规则:多方法使用时定义为成员变量 + @Before/@After 管理。
537
+
538
+ ### 2.7 ProcessSettleHelper 特殊 Mock
539
+
540
+ 由于 `ProcessSettleHelper` 内部使用了苍穹平台分布式缓存,常规 `Mockito.mockStatic()` 无法满足缓存使用需求,**必须**使用专用的 Helper:
541
+
542
+ ```java
543
+ MockedStatic<ProcessSettleHelper> processSettleHelper =
544
+ ProcessSettleMockHelper.mockDistributeSessionlessCache();
545
+ ```
546
+
547
+ - `ProcessSettleMockHelper` 是各工程各自在 **test 目录下**提供的辅助类
548
+ - 查找策略:在**当前被测类所属工程**的 `src/test/java/` 下搜索 `ProcessSettleMockHelper`
549
+ - 若当前工程下找不到该类,**主动告知用户**,由用户在对应工程 test 目录下添加
550
+ - **禁止**固定引用其他工程(如 formplugin)的 ProcessSettleMockHelper
551
+
552
+ ### 2.8 MockedConstruction(Mock 构造函数)
553
+
554
+ 当被测代码内部通过 `new Xxx()` 创建了某个对象,而该对象需要被 mock 时,使用 `Mockito.mockConstruction`:
555
+
556
+ ```java
557
+ @Test
558
+ public void testMethod() {
559
+ try (MockedConstruction<PushArgs> pushArgsMock =
560
+ Mockito.mockConstruction(PushArgs.class)) {
561
+
562
+ //step 执行被测方法(内部会 new PushArgs())
563
+ SomeUtil.doSomething(params);
564
+
565
+ //assert 验证 PushArgs 被正确构造和使用
566
+ assertEquals(1, pushArgsMock.constructed().size());
567
+ PushArgs constructed = pushArgsMock.constructed().get(0);
568
+ verify(constructed).setSomeProperty(any());
569
+ }
570
+ }
571
+ ```
572
+
573
+ - 适用场景:被测方法内部 `new` 了无法通过参数注入的对象
574
+ - 使用 **try-with-resources** 管理生命周期,自动关闭
575
+ - 可与 `MockedStatic` 同时使用(在同一个 try 块中声明)
576
+
577
+ ### 2.9 CommonMockObject 工具
578
+
579
+ `CommonMockObject` 提供了一组通用的 Mock 数据构建工具方法,所有工程均可使用:
580
+
581
+ | 方法 | 说明 | 适用范围 |
582
+ |------|------|--------|
583
+ | `CommonMockObject.mockMainEntityType()` | Mock 主实体类型 | 通用 |
584
+ | `CommonMockObject.getDynByString(jsonString)` | 从 JSON 字符串构造 DynamicObject | 通用 |
585
+ | `CommonMockObject.getDynsByString(jsonString)` | 从 JSON 字符串构造 DynamicObject[] | 通用 |
586
+ | `CommonMockObject.getDynMockerByMap(map)` | 从 Map 构造 DynamicObjectMocker(支持嵌套) | 通用 |
587
+ | `CommonMockObject.getDynByMap(map)` | 从 Map 构造 DynamicObject | 通用 |
588
+ | `CommonMockObject.getTestModel()` | 获取 Mock 的 IDataModel | formplugin(表单插件场景) |
589
+ | `CommonMockObject.getTestView()` | 获取 Mock 的 IFormView | formplugin(表单插件场景) |
590
+
591
+ 使用示例:
592
+
593
+ ```java
594
+ // 从 JSON 构造 DynamicObject(适用于复杂嵌套数据)
595
+ DynamicObject obj = CommonMockObject.getDynByString("{\"id\":1,\"billno\":\"TEST-001\"}");
596
+
597
+ // 从 Map 构造(适用于需要动态组装字段的场景)
598
+ Map<String, Object> map = new HashMap<>();
599
+ map.put("id", 1L);
600
+ map.put("status", "A");
601
+ DynamicObject obj = CommonMockObject.getDynByMap(map);
602
+
603
+ // Mock 主实体类型(opplugin 场景常用)
604
+ CommonMockObject.mockMainEntityType();
605
+ ```
606
+
607
+ ### 2.10 thenAnswer 动态返回模式
608
+
609
+ 当同一个 Mock 方法被不同参数多次调用,且需要根据入参动态返回不同结果时,使用 `thenAnswer` 代替 `thenReturn`:
610
+
611
+ ```java
612
+ // thenReturn:只能返回固定值
613
+ queryServiceHelper.when(() -> QueryServiceHelper.query(anyString(), anyString(), any()))
614
+ .thenReturn(fixedResult);
615
+
616
+ // thenAnswer:根据入参动态返回
617
+ queryServiceHelper.when(() -> QueryServiceHelper.query(anyString(), anyString(), any()))
618
+ .thenAnswer(invocation -> {
619
+ String entityName = invocation.getArgument(0); // 取第1个参数
620
+ if ("mmc_processplan".equals(entityName)) {
621
+ return mockProcessPlanData(); // 查工序计划 -> 返回工序计划数据
622
+ }
623
+ if ("mmc_sendwork".equals(entityName)) {
624
+ return mockSendWorkData(); // 查派工单 -> 返回派工单数据
625
+ }
626
+ return new DynamicObjectCollection(); // 其他 -> 返回空集合
627
+ });
628
+ ```
629
+
630
+ 适用场景:被测方法内部对同一个 ServiceHelper 发起多次不同参数的调用,每次期望不同结果。
631
+
632
+ ---
633
+
634
+ ## Step 3: 选择测试场景
635
+
636
+ 为每个被测方法设计以下场景:
637
+
638
+ | 场景类型 | 说明 | 示例 |
639
+ |---------|------|------|
640
+ | 正常路径 | 标准输入,预期正常输出 | 数据完整时校验通过 |
641
+ | 边界条件 | 空值 / 空集合 / 零值 | 分录为空、数量为0 |
642
+ | **边界值(重点)** | **比较运算符的临界点,必须覆盖「刚好满足」和「刚好不满足」两侧** | 见下表 |
643
+ | 异常路径 | 异常输入或依赖服务异常 | 查询返回 null、抛异常 |
644
+ | 业务规则 | 特定业务条件分支 | 单据状态不同的处理逻辑 |
645
+
646
+ ### 边界值覆盖要求
647
+
648
+ **必须为每个比较运算符设计恰好覆盖两侧的测试用例:**
649
+
650
+ | 比较运算符 | 必须覆盖的值 | 示例(可退回数量校验) |
651
+ |-----------|------------|---------------------|
652
+ | `> 0`(大于零) | 等于0(不通过)、大于0(通过) | qty=0 → 校验失败;qty=0.01 → 校验通过 |
653
+ | `>= 0`(大于等于零) | 等于0(通过)、小于0(不通过) | qty=0 → 通过;qty=-0.01 → 失败 |
654
+ | `a.compareTo(b) > 0` | a==b(不通过)、a>b(通过)、a<b(不通过) | rejectQty=canRejectQty(边界相等)、超出(失败)、未超出(通过) |
655
+ | `a.compareTo(b) >= 0` | a<b(不通过)、a==b(通过)、a>b(通过) | 同上,覆盖相等时通过的情况 |
656
+ | `size() > N` | size==N(不通过)、size==N+1(通过) | 分录恰好N条 vs N+1条 |
657
+ | `isEmpty()` | 空集合、恰好1条、多条 | entryIdSet 为空 / 1个 / 多个 |
658
+
659
+ **原则:临界值(boundary value)必须至少出现一个测试用例,不能只测「远离边界」的正常值。**
660
+
661
+ ### switch / 判断分支拆分策略
662
+
663
+ | 分支类型 | 测试方法拆分规则 | 示例 |
664
+ |---------|---------------|------|
665
+ | **switch 分支** | 每个 case **必须**定义为独立的测试方法 | `testPropertyChanged_CaseFieldA()`、`testPropertyChanged_CaseFieldB()` |
666
+ | switch case 内部多场景 | 允许在同一方法中测试多个场景(过于复杂时可进一步拆分) | 一个 case 内有 3 个 if 分支,可在一个方法中覆盖 |
667
+ | **if/else 判断分支** | 需覆盖所有条件组合(笛卡尔积),可合并到同一测试方法 | 2 个布尔条件 → 4 种组合,可用 4 组数据在一个方法中测试 |
668
+ | 被测方法调用的其他方法中的分支 | 同上,关联方法的判断分支也需要覆盖 | 被测方法内部调用了 `checkStatus()`,该方法的分支也需覆盖 |
669
+
670
+ ---
671
+
672
+ ## Step 4: 验证与运行
673
+
674
+ 测试类编写完成后,执行以下检查闭环:
675
+
676
+ 1. **代码静态分析(门控:发现问题必须先修复,不得直接运行 Gradle)**
677
+ - 确认 import 语句完整
678
+ - 确认所有 MockedStatic 都在 @After 中关闭
679
+ - 确认测试方法命名清晰(testXxx_Scenario 格式)
680
+ - 确认常量类/枚举类引用正确(非字面値替代)
681
+ - 确认 DynamicObject 断言使用基本値(非 null 判定)
682
+ - 确认每个 @Test 方法均有有效的 assert 或 verify 调用
683
+ - 确认 BaseTest 已预置的 MockedStatic 未重复声明
684
+ - 确认 MockedStatic 初始化顺序正确(super.before() 在前,自有 Mock 在后)
685
+ - **⚠️ 若以上任一项不满足,必须先修复,再执行第 2 步**
686
+
687
+ 2. **运行测试用例**
688
+
689
+ ### ⚡ Gradle 运行前环境快速判断(跳过无效探测)
690
+
691
+ **在运行 Gradle 前,先执行以下两项快速检查,任一失败则直接跳过 Gradle,提示用户在 IDE 中运行:**
692
+
693
+ | 检查项 | 检查方式 | 失败时处理 |
694
+ |--------|---------|----------|
695
+ | ① biz lib 中是否存在被测模块的 jar | `ls <biz路径>/<模块组>-formplugin*.jar`(或对应工程) | 不存在 → 跳过 Gradle,提示「请先在 IDE 中 Build 工程或运行 copytolib,再通过 IDE 运行测试」 |
696
+ | ② config.gradle 中的 biz 路径在本机是否真实存在 | 读取 `config.gradle` 中的 `biz` 路径,执行 `ls <biz路径>` | 路径不存在 → 跳过 Gradle,提示「config.gradle 路径与本机不符,请在 IDE 中运行测试」 |
697
+
698
+ > 本次优化来源:InsideAcceptEditPlugin 测试中,花费 ~8 分钟探测 gradle 可执行文件位置、
699
+ > 尝试构建依赖链,最终因 mmc-sfc-common.jar 未构建而失败,属于完全可预判的无效耗时。
700
+
701
+ 通过环境判断后,运行 Gradle test 任务验证(根据识别到的模块组前缀替换 `<模块组>`):
702
+ `gradle :<模块组>-common:test` / `gradle :<模块组>-business:test` / `gradle :<模块组>-opplugin:test` / `gradle :<模块组>-formplugin:test`
703
+ 例如 mmc-sfc 模块:`gradle :mmc-sfc-common:test` / `gradle :mmc-sfc-business:test` / ...
704
+
705
+ 3. **错误修复**
706
+ - 对出现的错误内容进行修复(如不完善的 mock 导致取值报错、断言与实际结果不匹配等)
707
+ - 修复后**重新运行**验证,直到用例全部通过
708
+ - 输出最终结果
709
+
710
+ ---
711
+
712
+ ## BaseTest 差异对比
713
+
714
+ 四个工程各有自己的 `BaseTest` / `BasePluginTest`,注意包名和预置 Mock 的差异。
715
+
716
+ **通用发现规则**:不同模块组的 BaseTest 包名遵循相同规律,**实际使用时必须先读取目标工程的 BaseTest 源码**,确认其预置了哪些 MockedStatic。
717
+
718
+ | 工程 | BaseTest 包名规律 | 查找路径 |
719
+ |--------|--------------|---------------|
720
+ | `<模块组>-common` | `kd.<包路径>.common.BaseTest` | `<模块组>-common/src/test/java/.../BaseTest.java` |
721
+ | `<模块组>-business` | `kd.<包路径>.business.BaseTest` | `<模块组>-business/src/test/java/.../BaseTest.java` |
722
+ | `<模块组>-opplugin` | `kd.<包路径>.opplugin.BaseTest` | `<模块组>-opplugin/src/test/java/.../BaseTest.java` |
723
+ | `<模块组>-formplugin` | `kd.<包路径>.formplugin.BasePluginTest` | `<模块组>-formplugin/src/test/java/.../BasePluginTest.java` |
724
+
725
+ **mmc-sfc 模块已知预置 Mock(作为参考)**:
726
+
727
+ | 工程 | BaseTest 包名 | 额外预置 Mock |
728
+ |--------|--------------|---------------|
729
+ | mmc-sfc-common | `kd.mmc.sfc.common.BaseTest` | SaveServiceHelper + EntityMetadataCache |
730
+ | mmc-sfc-business | `kd.mmc.sfc.business.BaseTest` | SaveServiceHelper + IDataModel/IFormView(CommonMockObject);BillUnitAndQtytHelper 仅声明字段未自动初始化 |
731
+ | mmc-sfc-opplugin | `kd.mmc.sfc.opplugin.BaseTest` | Singleton + BFTrackerServiceHelper + MutexUtils |
732
+ | mmc-sfc-formplugin | `kd.mmc.sfc.formplugin.BasePluginTest` | 表单插件专用参数与 mock(IFormView / IDataModel / AbstractFormPlugin 上下文等),详见 patterns/formplugin.md |
733
+
734
+ 三者(common / business / opplugin)都共同预置了 8 个 MockedStatic:OrgUnitServiceHelper、BillTypeParamHelper、ResManager、SystemParamServiceHelper、QueryServiceHelper、BusinessDataServiceHelper、OperationServiceHelper、BaseDataServiceHelper。其中 common 和 business 额外共享 SaveServiceHelper(opplugin 无此项)。
735
+
736
+ formplugin 的 BasePluginTest 独立管理,具体预置内容以实际文件为准。
737
+
738
+ ---
739
+
740
+ ## 常用 Mock 清单
741
+
742
+ 以下是苍穹模块单元测试中高频需要 Mock 的静态类:
743
+
744
+ | 静态类 | 用途 | 常见 Mock 方式 |
745
+ |--------|------|---------------|
746
+ | `QueryServiceHelper` | 数据库查询 | `.when(() -> query(...)).thenReturn(...)` |
747
+ | `BusinessDataServiceHelper` | 业务数据加载 | `.when(() -> loadSingle/load/loadFromCache(...)).thenReturn(...)` |
748
+ | `OperationServiceHelper` | 操作执行 | `.when(() -> executeOperate(...)).thenReturn(opResult)` |
749
+ | `SaveServiceHelper` | 数据保存 | `.when(() -> save/update(...)).thenReturn(...)` |
750
+ | `BaseDataServiceHelper` | 基础资料 | `.when(() -> getBaseDataFromCache(...)).thenReturn(...)` |
751
+ | `OrgUnitServiceHelper` | 组织单元 | 通常直接 mockStatic |
752
+ | `SystemParamServiceHelper` | 系统参数 | `.when(() -> getParameterValue(...)).thenReturn(...)` |
753
+ | `ResManager` | 多语言资源 | `.when(() -> loadKDString(...)).thenAnswer(inv -> inv.getArgument(0))` |
754
+ | `BillTypeParamHelper` | 单据类型参数 | 通常直接 mockStatic |
755
+ | `ProcessPlanHelper` | 工序计划业务 | 按需 Mock 具体方法 |
756
+ | `AttachmentServiceHelper` | 附件服务 | Mock upload/remove/getAttachments |
757
+ | `CacheFactory` | 缓存工厂 | Mock getCommonCacheFactory 链式调用 |
758
+ | `BFTrackerServiceHelper` | BOTP 追踪 | 通常直接 mockStatic |
759
+ | `EntityMetadataCache` | 元数据缓存 | 通常直接 mockStatic(common 工程常用) |
760
+ | `ConvertServiceHelper` | BOTP 转换服务 | Mock push/convert 方法 |
761
+ | `ProcessSettleHelper` | 工序结算(含分布式缓存) | **必须**用 `ProcessSettleMockHelper.mockDistributeSessionlessCache()` 创建,不可直接 mockStatic;ProcessSettleMockHelper 在各工程各自 test 目录下查找,找不到则告知用户添加(详见 2.7 节) |
762
+ | `DB`(kd.bos.db.DB) | 数据库操作 | **必须**先执行 ClassPool defrost 处理再 mockStatic(详见 2.6 节) |
763
+
764
+ ---
765
+
766
+ ## 高频混淆场景
767
+
768
+ | 场景 | ❌ 错误做法 | ✅ 正确做法 |
769
+ |------|-----------|-----------|
770
+ | 测试 protected 方法 | 改方法可见性为 public | 用 `ReflectHelper.invokeProtectedMethod()` |
771
+ | 测试 private static 方法 | 跳过或改可见性 | 用 `ReflectHelper.invokeStaticMethod()` |
772
+ | 校验器收集错误消息 | 直接调用 validate 不收集 | 匿名子类重写 `addMessage/addErrorMessage` 收集到 List |
773
+ | 构建分录数据 | 手动 new DynamicObject | 用 `DynamicObjectMocker` 链式构造 |
774
+ | Mock 静态方法后不关闭 | 忘记 close | @After 中**必须** close 所有 MockedStatic |
775
+ | ResManager 多语言 | 直接返回空字符串 | thenAnswer 返回第一个参数(保持可读性) |
776
+ | POJO 测试用 Mock | 给纯 POJO 加 MockedStatic | 直接 new 对象,无需任何 Mock |
777
+ | common 工具类 new 内部对象 | 无法 Mock 构造函数 | 用 `Mockito.mockConstruction()` Mock 构造函数 |
778
+ | 断言写法 | `assertTrue(true)` / `assertFalse(false)` 等假断言,或测试方法中根本没有任何 `assert`/`verify` 调用 | 断言被测方法的真实输出或用 `verify()` 校验交互;「提前返回」场景用 `verify(mock, never()).method()` 断言下游调用未被触发 |
779
+ | Mockito 重载方法歧义 | `any()` 无法区分 `method(DynamicObject)` 和 `method(List<Long>)` | 显式指定类型:`any(DynamicObject.class)` |
780
+ | Mockito varargs 参数匹配 | `verify(view).setEnable(eq(true), any(String[].class))` | `verify(view).setEnable(eq(true), anyString())`(varargs 用 `anyString()` 而非 `any(String[].class)`) |
781
+ | `any()` 在非 Object 类型参数上歧义 | `any()` 用于 `Date`/`Set`/`String` 等具体类型参数 | 必须明确类型:`any(Date.class)`、`any(Set.class)`、`anyString()` 等;`any()` 仅在参数类型为 `Object` 且无重载歧义时使用 |
782
+ | Mock 前未确认方法签名 | `anyLong()` 匹配 `String` 参数导致运行时失败 | **编写 mock 前必须读取被测方法的真实签名**,确保 `anyXxx()` 与参数类型一致 |
783
+ | `DynamicObjectCollection` 状态控制 | `new DynamicObjectCollection().add(item)` 后 `isEmpty()/size()` 不可靠(框架类需要 DynamicObjectType 初始化) | 当被测代码以 `isEmpty()/size()` 作为分支条件时,改用 `mock(DynamicObjectCollection.class)` 并显式 stub:`when(entries.isEmpty()).thenReturn(false); when(entries.size()).thenReturn(1)` |
784
+ | 点分隔字段名的 DynamicObject 访问 | `DynamicObjectMocker.add("entryentity.id", 100L)` 后 `obj.getLong("entryentity.id")` 抛 `ORMDesignException` | `DynamicObjectMocker` 创建的是 Simple 类型实体,点分隔的字段名会被解析为嵌套路径导航。**对于 QueryServiceHelper.query() 平展查询结果中含点号字段的 DynamicObject**,必须使用 `mock(DynamicObject.class)` + `when(obj.getLong/getBigDecimal("key")).thenReturn(value)` |
785
+ | Mock DB 类 | 直接 `Mockito.mockStatic(DB.class)` | 必须先执行 ClassPool defrost 处理(详见 2.6 节),否则可能因字节码冻结导致 mock 失败 |
786
+ | Mock ProcessSettleHelper | 直接 `Mockito.mockStatic(ProcessSettleHelper.class)` | 必须用 `ProcessSettleMockHelper.mockDistributeSessionlessCache()`(详见 2.7 节),在当前工程 test 目录下查找该 Helper,找不到则告知用户添加 |
787
+ | 常量/枚举类引用不到 | 用字面值 `"A"` 替代 `StatusEnum.APPROVED` | **禁止字面值替代**,主动告知用户补充常量/枚举类的文件位置或源码 |
788
+ | DynamicObject 空值断言 | `assertNull(obj.getString("field"))` | `getString()`、`getLong()`、`getInt()`、`getBigDecimal()`、`getBoolean()` 在苍穹平台默认配置下不返回 null(即使 `set(key, null)`,平台会转为基本值):空字符串用 `assertEquals("", obj.getString(...))`、空数值用 `assertEquals(0L, obj.getLong(...))`、空 BigDecimal 用 `assertEquals(BigDecimal.ZERO, obj.getBigDecimal(...))`、布尔用 `assertFalse(obj.getBoolean(...))`。若被测代码对 getter 结果做了 `null` 检查(如 `if (obj.getBigDecimal("qty") != null)`),说明该字段可能开启了「允许为空」,测试用例必须覆盖 null 和非 null 两个分支;无法判断时默认按非 null 处理,但在测试方法中添加注释 `// 假设字段未开启"允许为空",若实际可为 null 请告知`。`getDate(key)` 是例外,始终可能为 null,需用 `assertNull` 或具体日期值断言 |