kld-sdd 2.6.1 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +28 -21
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +36 -30
- package/templates/skills/kld-sdd/opsx-apply/implementer-prompt.md +27 -31
- package/templates/skills/kld-sdd/opsx-apply/reference.md +7 -38
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +8 -11
- package/templates/skills/kld-sdd/opsx-check/checklist.md +4 -10
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +2 -0
- package/templates/skills/kld-sdd/opsx-design/checklist.md +1 -0
- package/templates/skills/kld-sdd/opsx-propose/reference.md +8 -18
- package/templates/skills/kld-sdd/opsx-rules/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +2 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +26 -43
- package/templates/skills/kld-sdd/opsx-task/checklist.md +4 -10
- package/templates/skills/kld-sdd/opsx-task/reference.md +12 -6
- package/templates/skills/kld-sdd/opsx-tdd-anti-patterns/SKILL.md +79 -0
- package/templates/skills/kld-sdd/opsx-tdd-anti-patterns/reference.md +203 -0
- package/templates/skills/kld-sdd/opsx-tdd-core/SKILL.md +167 -0
- package/templates/skills/kld-sdd/opsx-tdd-core/checklist.md +55 -0
- package/templates/skills/kld-sdd/opsx-tdd-core/reference.md +146 -0
- package/templates/skills/kld-sdd/opsx-tdd-metrics/SKILL.md +73 -0
- package/templates/skills/kld-sdd/opsx-tdd-metrics/checklist.md +60 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/SKILL.md +95 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/cause-effect-clarity.md +19 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/clean-test-data.md +33 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/existing-test-awareness.md +17 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/given-when-then.md +44 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/good-test-qualities.md +32 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/mock-boundary.md +44 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/naming-conventions.md +37 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/no-logic-in-tests.md +30 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/one-test-one-scenario.md +23 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/parameterized-testing.md +56 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/prefer-public-apis.md +17 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/general/test-behaviors-not-methods.md +26 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/argument-matching.md +38 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/controller-test-rules.md +37 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/domain-service-rules.md +33 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/java-test-template.md +42 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/json-serialization.md +34 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/java/logging-rules.md +35 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/post-generation/compilation-verification.md +25 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/post-generation/execution-verification.md +28 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/python/py-test-template.md +40 -0
- package/templates/skills/kld-sdd/opsx-tdd-quality/rules/typescript/ts-test-template.md +45 -0
- package/templates/skills/kld-sdd/opsx-tdd-review/SKILL.md +66 -0
- package/templates/skills/kld-sdd/opsx-tdd-review/checklist.md +39 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/SKILL.md +29 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/controller-strategy.md +32 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/dag-generation-rules.md +20 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/des-step-annotation.md +36 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/exception-path-coverage.md +47 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/green-scope-declaration.md +45 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/green-yagni-fence.md +41 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/multi-validation-split.md +36 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/non-tdd-modules.md +17 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/refactor-checklist.md +45 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/task-type-definitions.md +23 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/tdd-strategy-selection.md +13 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/test-execution-gate.md +25 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/test-skeleton-telemetry.md +19 -0
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +4 -3
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "opsx-tdd-core 详细参考 — 执行步骤、拆分示例、DAG 规则、telemetry 模板。仅在需要详细执行指引时读取。"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# opsx-tdd-core — 详细参考
|
|
6
|
+
|
|
7
|
+
> 仅在需要详细执行指引时读取。日常流程见 `SKILL.md`。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## §1 RED 执行步骤
|
|
12
|
+
|
|
13
|
+
1. 读取 spec.md 场景和 design.md 设计
|
|
14
|
+
2. 编写测试代码:Given-When-Then 结构 + 真实断言
|
|
15
|
+
3. ⛔ **只写当前 RED 对应的测试方法**:不提前编写后续 RED 的测试方法。若测试文件已有前序 RED 的测试方法,仅追加当前 RED 的方法
|
|
16
|
+
4. 运行测试:`mvn test -Dtest=XxxTest#testMethodName`(或项目对应命令)
|
|
17
|
+
5. 确认测试失败:失败原因必须是"功能未实现"(如 NullPointerException、AssertionError)
|
|
18
|
+
6. 如果测试通过:说明测试无效或功能已存在,重新编写测试
|
|
19
|
+
7. 记录失败原因到任务状态
|
|
20
|
+
8. ⛔ **执行中断声明**:`🔴 RED-N 确认失败,原因:[具体原因]。现在进入 GREEN-N,仅实现让此测试通过的最少代码。`
|
|
21
|
+
|
|
22
|
+
**RED 测试质量标准(Mock 边界,不 Mock 行为)**:
|
|
23
|
+
- 禁止 mock 被测行为本身(Given 应准备真实前置条件)
|
|
24
|
+
- Mock 仅用于系统边界依赖(数据库 Mapper、HTTP 客户端等)
|
|
25
|
+
- RED 测试的 Given 必须是真实输入
|
|
26
|
+
- 判断标准:删掉被测类实现后测试是否仍因 mock 而通过?如果是→测试是假的
|
|
27
|
+
|
|
28
|
+
## §2 GREEN 执行步骤
|
|
29
|
+
|
|
30
|
+
1. 读取对应 RED 任务的失败原因
|
|
31
|
+
2. ⛔ **执行 GREEN Scope 声明**(见 `opsx-tdd-rules/rules/green-scope-declaration.md`):
|
|
32
|
+
- 列出当前 RED 测试的断言清单
|
|
33
|
+
- 列出 design.md 中本 DES 元素的完整流程步骤
|
|
34
|
+
- 标记步骤归属(✅ 属于当前 RED / ⛔ 属于后续 RED)
|
|
35
|
+
- 仅实现标记为 ✅ 的步骤
|
|
36
|
+
3. ⛔ **逐条确认 YAGNI 围栏**:读取 tasks.md 中本 GREEN 任务的 YAGNI 围栏声明,逐条确认"未实现 [后续 RED 的行为]:✅"
|
|
37
|
+
4. 编写最少代码让测试通过
|
|
38
|
+
5. 不提前实现没有测试要求的功能(YAGNI)
|
|
39
|
+
6. 禁止捆绑未测试的代码:
|
|
40
|
+
- RED 只测了 Service 方法 → GREEN 不得同时实现 Controller 接口
|
|
41
|
+
- RED 只测了一个行为点 → GREEN 不得同时实现其他行为点
|
|
42
|
+
- 需修改多个文件才能让测试通过 → 检查是否测试范围过大
|
|
43
|
+
7. 运行测试确认通过
|
|
44
|
+
8. 引入新功能但无对应测试 → 删除多余代码
|
|
45
|
+
9. ⛔ **执行 GREEN Scope 门禁**:
|
|
46
|
+
- 检查生产代码中是否有未被当前 RED 断言覆盖的逻辑分支
|
|
47
|
+
- 若存在且属于后续 RED 的行为 → 删除越界代码
|
|
48
|
+
- 重新运行测试确认仍绿
|
|
49
|
+
10. 自检:修改文件列表是否与 RED 测试覆盖范围一致?超出则报告 DONE_WITH_CONCERNS
|
|
50
|
+
|
|
51
|
+
## §3 REFACTOR 执行步骤
|
|
52
|
+
|
|
53
|
+
> 完整检查点见 `opsx-tdd-rules/rules/refactor-checklist.md`(7 项重构检查 + 跳过条件 + 量化标准)。
|
|
54
|
+
|
|
55
|
+
1. 在所有测试通过的状态下开始
|
|
56
|
+
2. 优化代码结构(提取方法、消除重复、改善命名)
|
|
57
|
+
3. 运行全部测试:`mvn test`(或项目对应命令)
|
|
58
|
+
4. 确认所有测试仍通过
|
|
59
|
+
5. 任何测试失败 → 回退重构,重新尝试
|
|
60
|
+
6. 执行 `opsx-tdd-rules/rules/refactor-checklist.md` 的 7 项检查
|
|
61
|
+
|
|
62
|
+
## §4 TDD 拆分示例
|
|
63
|
+
|
|
64
|
+
以认证模块为例,7 个场景拆为 7 对 RED+GREEN + 1 个 REFACTOR:
|
|
65
|
+
|
|
66
|
+
| TASK-ID | 行为点 | 类型 | 依赖 | 验收标准 |
|
|
67
|
+
|---------|--------|------|------|---------|
|
|
68
|
+
| TASK-05-RED-1 | 登录成功返回 Token | 测试-RED | TASK-03,04 | 测试运行失败,失败原因:AuthService 未实现 |
|
|
69
|
+
| TASK-05-GREEN-1 | 登录成功最小实现 | 实现-GREEN | RED-1 | RED-1 测试通过,未提前实现密码错误校验 |
|
|
70
|
+
| TASK-05-RED-2 | 密码错误返回 2001 | 测试-RED | GREEN-1 | 测试运行失败,失败原因:未校验密码错误 |
|
|
71
|
+
| TASK-05-GREEN-2 | 密码校验实现 | 实现-GREEN | RED-2 | RED-2 测试通过 |
|
|
72
|
+
| ... | ... | ... | ... | ... |
|
|
73
|
+
| TASK-05-REFACTOR | 认证模块重构 | 重构-REFACTOR | GREEN-6 | 全部 user-auth 测试仍绿,代码结构改善 |
|
|
74
|
+
|
|
75
|
+
关键区别:
|
|
76
|
+
- 每个 RED 任务带真实断言跑起来确实失败
|
|
77
|
+
- 每个 GREEN 任务只写让当前测试通过的最少代码
|
|
78
|
+
- 行为点粒度一次一个小步循环
|
|
79
|
+
- 重构独立成任务在全部绿灯后进行
|
|
80
|
+
|
|
81
|
+
## §5 Controller 策略示例
|
|
82
|
+
|
|
83
|
+
**策略 A**(Controller 拆 RED→GREEN,使用 @WebMvcTest):
|
|
84
|
+
|
|
85
|
+
| TASK-ID | 行为点 | 类型 | 验收标准 |
|
|
86
|
+
|---------|--------|------|---------|
|
|
87
|
+
| TASK-CTRL-RED-1 | POST /api/v1/auth/login 返回 Token | 测试-RED | @WebMvcTest 测试运行失败 |
|
|
88
|
+
| TASK-CTRL-GREEN-1 | AuthController 接线 | 实现-GREEN | RED 测试通过 |
|
|
89
|
+
|
|
90
|
+
**策略 B**(Controller 作为非 TDD 任务):
|
|
91
|
+
|
|
92
|
+
| TASK-ID | 类型 | 验收标准 |
|
|
93
|
+
|---------|------|---------|
|
|
94
|
+
| TASK-CTRL | 接口层(非 TDD) | Controller 编译通过,调用对应 Service 方法,返回 Result |
|
|
95
|
+
|
|
96
|
+
## §6 DAG 生成规则
|
|
97
|
+
|
|
98
|
+
> 完整规则见 `opsx-tdd-rules/rules/dag-generation-rules.md`,此处仅保留快速参考。
|
|
99
|
+
|
|
100
|
+
| test-strategy | DAG 规则 |
|
|
101
|
+
|---------------|---------|
|
|
102
|
+
| `tdd` | 每个行为点拆为 RED→GREEN 对,GREEN Depends-On RED;每 3-5 个行为点后可插入 REFACTOR(Depends-On 前一个 GREEN);模块末尾可加 VERIFY(Depends-On 最后一个 GREEN/REFACTOR) |
|
|
103
|
+
| `impl-first` | 实现任务在前,测试任务 Depends-On 实现任务 |
|
|
104
|
+
| `none` | 不生成测试任务,仅编译检查 |
|
|
105
|
+
|
|
106
|
+
## §7 非 TDD 模块规则
|
|
107
|
+
|
|
108
|
+
> 完整规则见 `opsx-tdd-rules/rules/non-tdd-modules.md`,此处仅保留快速参考。
|
|
109
|
+
|
|
110
|
+
以下模块不需要红绿循环,按常规任务处理:
|
|
111
|
+
- 前端 UI 页面(Vue 组件)→ UI层任务
|
|
112
|
+
- 项目脚手架/配置 → 配置任务
|
|
113
|
+
- 数据库迁移脚本(SQL DDL)→ 数据层任务
|
|
114
|
+
- 前端路由/状态管理/API 封装 → UI层任务
|
|
115
|
+
- Controller 层(可选)→ 接口层任务(当选择策略 B 时)
|
|
116
|
+
|
|
117
|
+
## §8 Telemetry 模板
|
|
118
|
+
|
|
119
|
+
**TDD 测试骨架任务**(`task_kind:"test-skeleton"`):
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
node skywalk-sdd/log.cjs record --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 测试骨架完成(TDD 红灯)" --details-json='{"task_kind":"test-skeleton","test_results":{"command":"<实际测试命令>","passed":0,"failed":<红灯数>,"skipped":0,"duration_ms":0}}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**实现任务**(不带 `task_kind`):
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
node skywalk-sdd/log.cjs record --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json='{"files_changed":[],"test_results":{"command":"<实际测试命令>","passed":<通过数>,"failed":0,"skipped":0,"duration_ms":0}}'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## §9 单元测试真实执行
|
|
132
|
+
|
|
133
|
+
当 `test-strategy` 为 `tdd` 或 `impl-first` 时:
|
|
134
|
+
|
|
135
|
+
1. 在结束 apply 或执行收尾之前,必须真实运行单元测试命令
|
|
136
|
+
2. 须留下可核验 telemetry 证据(任选其一):
|
|
137
|
+
- `node skywalk-sdd/log.cjs record --type=test_result ...`(`test_results.command` 非空,且 `passed`/`failed`/`duration_ms` 有实际值)
|
|
138
|
+
- `task_update` 的 `details-json` 中 `test_results` 含真实执行数据
|
|
139
|
+
- 或单独运行 `/opsx-test` 并完成 `command=test` 的 `stage_end`
|
|
140
|
+
3. `sdd-apply-test-gate.cjs` 会在 `log.cjs end`、`apply-worktree-finish`、会话 Stop 时自动校验,无证据则阻断
|
|
141
|
+
|
|
142
|
+
| test-strategy | 行为 |
|
|
143
|
+
|---------------|------|
|
|
144
|
+
| `tdd` | 无测试证据不得结束 apply / 不得 finish worktree |
|
|
145
|
+
| `impl-first` | 同上,实现后必须补跑并记录 |
|
|
146
|
+
| `none` | 跳过 |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opsx-tdd-metrics
|
|
3
|
+
description: "度量分析层 — 测试质量量化度量:隔离评分、命名评分、测试异味检测、覆盖率缺口分析。当需要量化测试质量时引用本技能。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# opsx-tdd-metrics — 度量分析层
|
|
7
|
+
|
|
8
|
+
> **定位**:测试质量的量化度量,提供可计算的评分。
|
|
9
|
+
> **参考来源**:tdd-guide `metrics_calculator.py`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## §1 隔离评分(0-100)
|
|
14
|
+
|
|
15
|
+
> 初始分 100,按以下项扣分/加分,最低 0 分。详细计算见 `./checklist.md` §A。
|
|
16
|
+
|
|
17
|
+
| 惩罚项 | 扣分 |
|
|
18
|
+
|--------|------|
|
|
19
|
+
| 全局状态使用 | -20 |
|
|
20
|
+
| 不匹配的 setup/cleanup | -15 |
|
|
21
|
+
| 测试间共享可变状态 | -15 |
|
|
22
|
+
|
|
23
|
+
| 奖励项 | 加分(上限 100) |
|
|
24
|
+
|--------|------|
|
|
25
|
+
| 正确使用 mock | +10 |
|
|
26
|
+
| 每个测试自包含 | +10 |
|
|
27
|
+
|
|
28
|
+
## §2 命名质量评分(0-100)
|
|
29
|
+
|
|
30
|
+
> 初始分 100,按以下项扣分,最低 0 分。详细计算见 `./checklist.md` §B。
|
|
31
|
+
|
|
32
|
+
检查项:
|
|
33
|
+
- 名称长度(太长或太短扣分)
|
|
34
|
+
- 描述性词汇(should/when/given/returns/throws/handles)
|
|
35
|
+
- 避免通用名称(test1、testCalc)
|
|
36
|
+
- 符合 `{method}_{state}_{outcome}` 格式
|
|
37
|
+
- 不暴露实现细节
|
|
38
|
+
|
|
39
|
+
## §3 测试异味检测
|
|
40
|
+
|
|
41
|
+
| 异味 | 检测条件 | 严重程度 |
|
|
42
|
+
|------|---------|---------|
|
|
43
|
+
| 断言轮盘赌 | 单测试 >5 个断言 | 中 |
|
|
44
|
+
| 缺失断言 | 无 `assert`/`expect` | 高 |
|
|
45
|
+
| sleep 使用 | `sleep`/`wait` | 高 |
|
|
46
|
+
| 条件逻辑 | `if` 语句 | 中 |
|
|
47
|
+
| 弱断言 | 只检查 truthy | 中 |
|
|
48
|
+
|
|
49
|
+
## §4 慢测试检测
|
|
50
|
+
|
|
51
|
+
>100ms 标记为慢测试。
|
|
52
|
+
|
|
53
|
+
## §5 不稳定测试检测
|
|
54
|
+
|
|
55
|
+
失败率 >10% 标记为 flaky。
|
|
56
|
+
|
|
57
|
+
## §6 可测试性评分(0-100)
|
|
58
|
+
|
|
59
|
+
> 基于被测代码的指标,详细计算见 `./checklist.md` §C。
|
|
60
|
+
|
|
61
|
+
| 指标 | 计算方式 | 阈值 |
|
|
62
|
+
|------|---------|------|
|
|
63
|
+
| 圈复杂度 | if/for/while/case/catch/&&/|| 决策点数 | >10 扣 10 分/点 |
|
|
64
|
+
| 依赖数量 | 构造器/setter 注入的依赖数 | >5 扣 5 分/个 |
|
|
65
|
+
| 函数大小 | 最长方法的行数 | >50 扣 2 分/行 |
|
|
66
|
+
|
|
67
|
+
## §7 覆盖率缺口分析
|
|
68
|
+
|
|
69
|
+
| 优先级 | 覆盖率范围 | 说明 |
|
|
70
|
+
|--------|-----------|------|
|
|
71
|
+
| P0 | <40% | 关键路径,必须补充 |
|
|
72
|
+
| P1 | 60-80% | 重要路径,建议补充 |
|
|
73
|
+
| P2 | 80%+ | 一般路径,可选补充 |
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "opsx-tdd-metrics 自检清单 — 隔离评分、命名评分、可测试性评分的计算口径。仅在量化测试质量时读取。"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# opsx-tdd-metrics — 自检清单
|
|
6
|
+
|
|
7
|
+
> 仅在量化测试质量时读取。日常流程见 `SKILL.md`。
|
|
8
|
+
|
|
9
|
+
## §A 隔离评分(0-100)
|
|
10
|
+
|
|
11
|
+
**初始分:100,按以下项扣分/加分,最低 0 分。**
|
|
12
|
+
|
|
13
|
+
| 惩罚项 | 扣分 |
|
|
14
|
+
|--------|------|
|
|
15
|
+
| 全局状态使用 | -20 |
|
|
16
|
+
| 不匹配的 setup/cleanup | -15 |
|
|
17
|
+
| 测试间共享可变状态 | -15 |
|
|
18
|
+
|
|
19
|
+
| 奖励项 | 加分(上限 100) |
|
|
20
|
+
|--------|------|
|
|
21
|
+
| 正确使用 mock(仅 Mock 系统边界) | +10 |
|
|
22
|
+
| 每个测试自包含(无隐式依赖) | +10 |
|
|
23
|
+
|
|
24
|
+
## §B 命名质量评分(0-100)
|
|
25
|
+
|
|
26
|
+
**初始分:100,按以下项扣分,最低 0 分。**
|
|
27
|
+
|
|
28
|
+
| 惩罚项 | 扣分 |
|
|
29
|
+
|--------|------|
|
|
30
|
+
| 名称长度 >60 字符 | -10 |
|
|
31
|
+
| 名称长度 <10 字符 | -10 |
|
|
32
|
+
| 缺少描述性词汇(should/when/given/returns/throws/handles) | -15 |
|
|
33
|
+
| 使用通用名称(test1、testCalc) | -20 |
|
|
34
|
+
| 不符合 `{method}_{state}_{outcome}` 格式 | -15 |
|
|
35
|
+
| 暴露实现细节(如 usesStreamApi) | -10 |
|
|
36
|
+
|
|
37
|
+
## §C 可测试性评分(0-100)
|
|
38
|
+
|
|
39
|
+
**基于被测代码(非测试代码)的以下指标:**
|
|
40
|
+
|
|
41
|
+
| 指标 | 计算方式 | 阈值 |
|
|
42
|
+
|------|---------|------|
|
|
43
|
+
| 圈复杂度 | if/for/while/case/catch/&&/|| 决策点数 | >10 扣 10 分/点 |
|
|
44
|
+
| 依赖数量 | 构造器/setter 注入的依赖数 | >5 扣 5 分/个 |
|
|
45
|
+
| 函数大小 | 最长方法的行数 | >50 扣 2 分/行 |
|
|
46
|
+
|
|
47
|
+
## §D 覆盖率缺口分析
|
|
48
|
+
|
|
49
|
+
| 优先级 | 覆盖率范围 | 说明 |
|
|
50
|
+
|--------|-----------|------|
|
|
51
|
+
| P0 | <40% | 关键路径,必须补充 |
|
|
52
|
+
| P1 | 60-80% | 重要路径,建议补充 |
|
|
53
|
+
| P2 | 80%+ | 一般路径,可选补充 |
|
|
54
|
+
|
|
55
|
+
## §E 慢测试与不稳定测试
|
|
56
|
+
|
|
57
|
+
| 指标 | 阈值 | 说明 |
|
|
58
|
+
|------|------|------|
|
|
59
|
+
| 慢测试 | >100ms | 标记为慢测试 |
|
|
60
|
+
| 不稳定测试 | 失败率 >10% | 标记为 flaky |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opsx-tdd-quality
|
|
3
|
+
description: "单测代码质量层 — Mock 边界矩阵、测试命名规范、Given-When-Then 结构、Java 测试规则等。当编写或审查测试代码时引用本技能。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# opsx-tdd-quality — 单测代码质量层
|
|
7
|
+
|
|
8
|
+
> **定位**:单元测试代码的质量标准,不涉及流程,只关注"写出来的测试代码本身是否高质量"。
|
|
9
|
+
> **参考来源**:unit-tests-skills(14 通用规则 + 6 Java 规则)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## §1 好测试的四大品质(CCCR 框架)
|
|
14
|
+
|
|
15
|
+
| 品质 | 说明 | 检查标准 |
|
|
16
|
+
|------|------|---------|
|
|
17
|
+
| **Clarity(清晰性)** | 一眼就能读懂 | 10 秒内能理解吗? |
|
|
18
|
+
| **Completeness(完整性)** | 包含理解测试所需的全部信息 | 所有相关数据在测试中可见,不依赖隐藏 setup |
|
|
19
|
+
| **Conciseness(简洁性)** | 只包含与场景相关的信息 | 无关细节已隐藏(用 helper) |
|
|
20
|
+
| **Resilience(韧性)** | 不因无关代码变更而失败 | 测试行为而非实现,使用公共 API |
|
|
21
|
+
|
|
22
|
+
## §2 Mock 边界矩阵
|
|
23
|
+
|
|
24
|
+
> 完整规则见 `rules/general/mock-boundary.md`,此处仅保留快速参考。
|
|
25
|
+
|
|
26
|
+
| 应该 Mock | 用真实对象 | 绝不 Mock |
|
|
27
|
+
|-----------|-----------|-----------|
|
|
28
|
+
| Repository / DAO | DTO / 值对象 | 被测系统(SUT)本身 |
|
|
29
|
+
| 外部服务客户端 | 领域实体(大多数情况) | |
|
|
30
|
+
| 消息生产者 | 工具类 | |
|
|
31
|
+
| 缓存服务 | Mapper(通常) | |
|
|
32
|
+
| 任何 I/O 操作 | | |
|
|
33
|
+
|
|
34
|
+
**判断标准**:删掉被测类实现后测试是否仍因 mock 而通过?如果是→测试是假的。
|
|
35
|
+
|
|
36
|
+
## §3 测试命名规范
|
|
37
|
+
|
|
38
|
+
> 完整规则见 `rules/general/naming-conventions.md`,此处仅保留快速参考。
|
|
39
|
+
|
|
40
|
+
格式:`{method}_{state}_{outcome}`
|
|
41
|
+
|
|
42
|
+
- ✅ `login_wrongPassword_returns2001`
|
|
43
|
+
- ❌ `testCalculate()` — 太模糊
|
|
44
|
+
|
|
45
|
+
## §4 Given-When-Then 结构
|
|
46
|
+
|
|
47
|
+
> 完整规则见 `rules/general/given-when-then.md`,此处仅保留快速参考。
|
|
48
|
+
|
|
49
|
+
每个测试必须有清晰的三段式结构,用注释标注。`@BeforeEach` 仅用于基础设施 setup。
|
|
50
|
+
|
|
51
|
+
## §5 测试聚焦
|
|
52
|
+
|
|
53
|
+
> 完整规则见 `rules/general/one-test-one-scenario.md`,此处仅保留快速参考。
|
|
54
|
+
|
|
55
|
+
一测一场景。测试名包含 "and" 是反模式信号。
|
|
56
|
+
|
|
57
|
+
## §6 测试中不要放逻辑
|
|
58
|
+
|
|
59
|
+
> 完整规则见 `rules/general/no-logic-in-tests.md`,此处仅保留快速参考。
|
|
60
|
+
|
|
61
|
+
**KISS > DRY**。禁止:循环、条件、字符串拼接、计算在断言中。
|
|
62
|
+
|
|
63
|
+
## §7 规则索引
|
|
64
|
+
|
|
65
|
+
通用规则(适用所有语言):
|
|
66
|
+
- `rules/general/naming-conventions.md` — 测试命名规范
|
|
67
|
+
- `rules/general/given-when-then.md` — Given-When-Then 结构
|
|
68
|
+
- `rules/general/test-behaviors-not-methods.md` — 测试行为而非方法
|
|
69
|
+
- `rules/general/one-test-one-scenario.md` — 一测一场景
|
|
70
|
+
- `rules/general/no-logic-in-tests.md` — 测试中不要放逻辑
|
|
71
|
+
- `rules/general/prefer-public-apis.md` — 优先测试公共 API
|
|
72
|
+
- `rules/general/clean-test-data.md` — 干净地创建测试数据
|
|
73
|
+
- `rules/general/cause-effect-clarity.md` — 保持因果清晰
|
|
74
|
+
- `rules/general/good-test-qualities.md` — 好测试的四大品质
|
|
75
|
+
- `rules/general/mock-boundary.md` — Mock 边界矩阵
|
|
76
|
+
- `rules/general/existing-test-awareness.md` — 已有测试感知
|
|
77
|
+
- `rules/general/parameterized-testing.md` — 参数化测试指导
|
|
78
|
+
|
|
79
|
+
Java 专用规则:
|
|
80
|
+
- `rules/java/java-test-template.md` — Java 测试模板
|
|
81
|
+
- `rules/java/argument-matching.md` — Mockito 参数匹配
|
|
82
|
+
- `rules/java/json-serialization.md` — JSON 序列化
|
|
83
|
+
- `rules/java/logging-rules.md` — 日志输出验证
|
|
84
|
+
- `rules/java/domain-service-rules.md` — 领域/服务单元测试规则
|
|
85
|
+
- `rules/java/controller-test-rules.md` — 控制器测试规则
|
|
86
|
+
|
|
87
|
+
TypeScript 专用规则:
|
|
88
|
+
- `rules/typescript/ts-test-template.md` — TypeScript 测试模板(Vitest/Jest)
|
|
89
|
+
|
|
90
|
+
Python 专用规则:
|
|
91
|
+
- `rules/python/py-test-template.md` — Python 测试模板(pytest)
|
|
92
|
+
|
|
93
|
+
生成后验证:
|
|
94
|
+
- `rules/post-generation/compilation-verification.md` — 编译验证
|
|
95
|
+
- `rules/post-generation/execution-verification.md` — 执行验证
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 保持因果清晰
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心规则
|
|
6
|
+
|
|
7
|
+
效果应紧跟原因。避免依赖远处的 setup 代码。
|
|
8
|
+
|
|
9
|
+
## 关键指南
|
|
10
|
+
|
|
11
|
+
1. 如果 setup 与理解测试相关,就放在测试方法内
|
|
12
|
+
2. `@BeforeEach` 只用于**基础设施 setup**(mock、容器),不用于测试数据
|
|
13
|
+
3. 避免共享可变状态——每个测试设置自己的数据
|
|
14
|
+
4. 保持测试自包含——读者不需要看别处
|
|
15
|
+
|
|
16
|
+
## @BeforeEach 适用场景
|
|
17
|
+
|
|
18
|
+
启动 MockWebServer、创建 SUT 实例等基础设施。
|
|
19
|
+
测试特定数据(如 mock 返回值)应放在测试方法内。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 干净地创建测试数据
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 三大要点
|
|
6
|
+
|
|
7
|
+
1. **使用 Helper 函数**:隐藏无关细节,提高可读性
|
|
8
|
+
2. **使用 Builder 模式**:当 helper 参数过多时
|
|
9
|
+
3. **绝不依赖 Helper 的默认值**:如果测试依赖某个值,即使与 helper 默认值相同也要显式设置
|
|
10
|
+
|
|
11
|
+
## 正例
|
|
12
|
+
|
|
13
|
+
```java
|
|
14
|
+
// Helper 返回带必要默认值的 builder
|
|
15
|
+
newCompany().employeesCount(2).boardMembersCount(2).build()
|
|
16
|
+
|
|
17
|
+
// 显式设置测试依赖的值
|
|
18
|
+
newCompany().type(Type.PUBLIC).build()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 反例
|
|
22
|
+
|
|
23
|
+
```java
|
|
24
|
+
// 设置了所有字段,但测试只关心 name
|
|
25
|
+
new ShoppingCart(new DefaultRoundingStrategy(), "unused", NORMAL, false, false, TimeZone.getTimeZone("UTC"), null)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Helper 指南
|
|
29
|
+
|
|
30
|
+
1. 描述性命名——`createProductWithCategory("Office")` 而非 `createProduct()`
|
|
31
|
+
2. 只暴露相关参数
|
|
32
|
+
3. 保持简单——无业务逻辑
|
|
33
|
+
4. 复杂对象考虑 builder
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 已有测试感知
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 生成前必做
|
|
6
|
+
|
|
7
|
+
1. 搜索 `{ClassName}Test` 或 `{ClassName}Tests`
|
|
8
|
+
2. 如果找到:**不要创建新类**,只添加缺失的测试方法,保留已有结构/导入/helper,遵循已有模式
|
|
9
|
+
3. 如果没找到:扫描同包中 2-3 个邻近测试类学习项目规范
|
|
10
|
+
|
|
11
|
+
## 需要匹配的方面
|
|
12
|
+
|
|
13
|
+
- 断言库:Hamcrest vs AssertJ——不要混用
|
|
14
|
+
- 测试数据模式:项目有 `TestDataFactory` 或 builder 就用它们
|
|
15
|
+
- 基础测试类:继承 `BaseTest` 或 `AbstractIntegrationTest` 的模式
|
|
16
|
+
- 静态导入风格
|
|
17
|
+
- 注释风格:`// given / when / then` vs `// arrange / act / assert`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Given-When-Then 结构
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心规则
|
|
6
|
+
|
|
7
|
+
每个测试必须有清晰的 setup/action/verification 三段式结构,用注释标注。
|
|
8
|
+
|
|
9
|
+
## 正例
|
|
10
|
+
|
|
11
|
+
```java
|
|
12
|
+
@Test
|
|
13
|
+
void create_validName_returnsId() {
|
|
14
|
+
// Given
|
|
15
|
+
when(publisherMapper.selectOne(any())).thenReturn(null);
|
|
16
|
+
doAnswer(invocation -> {
|
|
17
|
+
Publisher p = invocation.getArgument(0);
|
|
18
|
+
p.setId(1L);
|
|
19
|
+
return 1;
|
|
20
|
+
}).when(publisherMapper).insert(any(Publisher.class));
|
|
21
|
+
|
|
22
|
+
// When
|
|
23
|
+
Long result = publisherService.create("人民邮电出版社");
|
|
24
|
+
|
|
25
|
+
// Then
|
|
26
|
+
assertNotNull(result);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 反例
|
|
31
|
+
|
|
32
|
+
```java
|
|
33
|
+
@Test
|
|
34
|
+
void testCreate() {
|
|
35
|
+
Long result = publisherService.create("test");
|
|
36
|
+
assertNotNull(result);
|
|
37
|
+
// 没有 Given-When-Then 结构,难以理解测试意图
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## @BeforeEach 使用规则
|
|
42
|
+
|
|
43
|
+
`@BeforeEach` 仅用于**基础设施 setup**(mock、容器),不用于测试数据。
|
|
44
|
+
测试特定数据(如 mock 返回值)应放在测试方法内。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 好测试的四大品质(CCCR 框架)
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 四大品质
|
|
6
|
+
|
|
7
|
+
1. **Clarity(清晰性)**:一眼就能读懂
|
|
8
|
+
- 测试名描述场景
|
|
9
|
+
- Given-When-Then 结构明显
|
|
10
|
+
- 不需要看别处就能理解
|
|
11
|
+
- 检查标准:10 秒内能理解吗?
|
|
12
|
+
|
|
13
|
+
2. **Completeness(完整性)**:包含理解测试所需的全部信息
|
|
14
|
+
- 反模式:依赖类级常量和 `@BeforeEach` 中的隐藏 setup
|
|
15
|
+
- 正确:所有相关数据在测试中可见
|
|
16
|
+
|
|
17
|
+
3. **Conciseness(简洁性)**:只包含与场景相关的信息
|
|
18
|
+
- 隐藏无关细节(用 helper)
|
|
19
|
+
- 反模式:设置 user 的所有字段,但测试只关心 name
|
|
20
|
+
|
|
21
|
+
4. **Resilience(韧性)**:不因无关代码变更而失败
|
|
22
|
+
- 测试行为而非实现
|
|
23
|
+
- 使用公共 API
|
|
24
|
+
- 不过度指定 mock 交互
|
|
25
|
+
- 不依赖字段顺序或格式
|
|
26
|
+
|
|
27
|
+
## 检查清单
|
|
28
|
+
|
|
29
|
+
- [ ] 清晰性:10 秒内能理解吗?
|
|
30
|
+
- [ ] 完整性:所有相关信息都在测试中吗?
|
|
31
|
+
- [ ] 简洁性:无关信息是否已隐藏?
|
|
32
|
+
- [ ] 韧性:重构后测试还能存活吗?
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Mock 边界矩阵
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
测试必须验证真实行为,而非 mock 行为。Mock 是隔离的手段,不是被测试的对象。
|
|
8
|
+
|
|
9
|
+
## Mock 边界矩阵
|
|
10
|
+
|
|
11
|
+
| 应该 Mock | 用真实对象 | 绝不 Mock |
|
|
12
|
+
|-----------|-----------|-----------|
|
|
13
|
+
| Repository / DAO | DTO / 值对象 | 被测系统(SUT)本身 |
|
|
14
|
+
| 外部服务客户端 | 领域实体(大多数情况) | |
|
|
15
|
+
| 消息生产者 | 工具类 | |
|
|
16
|
+
| 缓存服务 | Mapper(通常) | |
|
|
17
|
+
| 任何 I/O 操作 | | |
|
|
18
|
+
|
|
19
|
+
## 正例
|
|
20
|
+
|
|
21
|
+
```java
|
|
22
|
+
// ✅ Mock Mapper(DB 边界)
|
|
23
|
+
@Mock
|
|
24
|
+
private UserMapper userMapper;
|
|
25
|
+
|
|
26
|
+
// ✅ 使用真实 JwtUtil + 真实 invalid token
|
|
27
|
+
private JwtUtil jwtUtil = new JwtUtil("real-secret-key-at-least-256-bits-long", 7200, 604800);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 反例
|
|
31
|
+
|
|
32
|
+
```java
|
|
33
|
+
// ❌ Mock JwtUtil.parseToken() — 被测行为的实现
|
|
34
|
+
@Mock
|
|
35
|
+
private JwtUtil jwtUtil;
|
|
36
|
+
when(jwtUtil.parseToken("invalid")).thenThrow(...);
|
|
37
|
+
|
|
38
|
+
// ❌ Mock PasswordEncoder 后只验证"密码不等于明文" — 弱断言
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 判断标准
|
|
42
|
+
|
|
43
|
+
如果删掉被测类的实现(方法体清空),测试是否仍然因为 mock 而通过?
|
|
44
|
+
如果是 → 测试是假的,必须重写。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 测试命名规范
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心规则
|
|
6
|
+
|
|
7
|
+
测试方法命名格式:`{testedMethod}_{givenState}_{expectedOutcome}`
|
|
8
|
+
|
|
9
|
+
## 命名指南
|
|
10
|
+
|
|
11
|
+
1. 状态要具体——"validProducts" 而非 "goodInput"
|
|
12
|
+
2. 结果要具体——"returns401" 而非 "fails"
|
|
13
|
+
3. 使用领域语言——"unauthorized" 而非 "noToken"
|
|
14
|
+
4. 避免技术术语——描述行为而非实现
|
|
15
|
+
|
|
16
|
+
## 正例
|
|
17
|
+
|
|
18
|
+
```java
|
|
19
|
+
@Test
|
|
20
|
+
void login_wrongPassword_returns2001() { ... }
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
void create_duplicateName_returns2006() { ... }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 反例
|
|
27
|
+
|
|
28
|
+
```java
|
|
29
|
+
@Test
|
|
30
|
+
void testCalculate() { ... } // 太模糊
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
void calculateTotal_validProducts() { ... } // 没有描述结果
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
void calculateTotal_usesStreamApi_returnsSum() { ... } // 暴露实现细节
|
|
37
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 测试中不要放逻辑
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
**KISS > DRY** — 在测试中,简单性比避免重复更重要。
|
|
8
|
+
|
|
9
|
+
## 禁止的模式
|
|
10
|
+
|
|
11
|
+
- 循环:`for (int i = 0; i < users.size(); i++) { assertThat(...) }`
|
|
12
|
+
- 条件:`if (response.isSuccessful()) { assertThat(...) }`
|
|
13
|
+
- 字符串拼接:`assertThat(result).isEqualTo("Hello, " + userName + "!")`
|
|
14
|
+
- 计算:`assertThat(total).isEqualTo(price * quantity + tax)`
|
|
15
|
+
|
|
16
|
+
## 正确做法
|
|
17
|
+
|
|
18
|
+
- 使用字面值——`assertThat(result).isEqualTo("Hello, John!")`
|
|
19
|
+
- 预计算的期望值——`int expectedTotal = 115;`
|
|
20
|
+
- 用 `assertThat(users).extracting(User::isActive).containsOnly(true)` 代替循环
|
|
21
|
+
|
|
22
|
+
## 逻辑隐藏 Bug 的经典案例
|
|
23
|
+
|
|
24
|
+
```java
|
|
25
|
+
// 错误:字符串拼接隐藏了 bug(结果为 "//u/0/photos")
|
|
26
|
+
assertThat(photosPageUrl).isEqualTo(baseUrl + "/u/0/photos");
|
|
27
|
+
|
|
28
|
+
// 正确:字面值让 bug 显而易见
|
|
29
|
+
assertThat(actualUrl).isEqualTo("http://photos.google.com/u/0/photos");
|
|
30
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 一测一场景
|
|
2
|
+
|
|
3
|
+
> 影响等级:HIGH
|
|
4
|
+
|
|
5
|
+
## 核心规则
|
|
6
|
+
|
|
7
|
+
每个测试只验证一个特定场景。多个场景混在一个测试中会导致失败难以诊断。
|
|
8
|
+
|
|
9
|
+
## 反模式信号
|
|
10
|
+
|
|
11
|
+
- 测试名包含 "and"(如 `testDepositAndWithdraw`)
|
|
12
|
+
- 多个 "When" 或 "Act" 段
|
|
13
|
+
- 断言之间有状态变化
|
|
14
|
+
- 难以简洁命名
|
|
15
|
+
- 测试超过 10-15 行
|
|
16
|
+
|
|
17
|
+
## 多断言何时可以
|
|
18
|
+
|
|
19
|
+
当验证**同一个行为**的多个属性时可以。例如验证用户创建行为时,同时断言 id 不为 null、email 正确、name 正确、createdAt 不为 null——这些都是验证"用户创建"这一个行为。
|
|
20
|
+
|
|
21
|
+
## 拆分判断标准
|
|
22
|
+
|
|
23
|
+
问自己"如果这个测试失败,我能确切知道哪个场景坏了吗?"如果不能,就拆分。
|