# TCG 测试用例生成系统 — 说明文档

> TCG = Test Case Generation。一套把**需求 SPEC** 自动转换为**自带证据的测试用例 md** 的流水线,打包为 `hmos-test-case-generation` skill(编排器 `SKILL.md`)+ 两个核心子 agent(`generator` 生成 / `validator` 复核)+ 非 LLM 校验脚本(`tools/validate.ts`)+ 三方共享契约(`references/contract.md`)。
> 本文档说明系统的整体架构、各文件职责、数据流、调用方式与失败处理。单个 skill / agent 的完整规则请直接看对应文件,本文档只做导航与总览。

---

## 1. 这套系统解决什么问题

TCG 的主流程是测试用例生成:输入一份 SPEC(`## 场景N` 原子场景块,由上游 `spec-generate` 拆好),可选读取 UI 元素描述(`ui_elements.json`)和参考资料,为每个场景生成自带四诚实证据的测试用例:

- `test_case.md` — 主测试用例(S3 逐批写,内嵌证据)
- `pre_test_case.md` — 前置用例(S3 按批累计,S6 收口;存在「见前置用例」时必须产出,无命中时可省略)
- `review_notes.md` — 单一人工伴随件(S6 合并:阻塞区 + 非阻塞区合一)

下游由 `hmos-integration-test` skill 接力,把 `test_case.md` 转成可执行的 `testcases.json`。

`ui_elements.json` 是 TCG 的软参照输入,不是测试用例交付件本身。缺少 `ui-elements-path` 但给了 `package` 或 `android-project-dir` 时,TCG 可先运行 viewtree-based UI elements mapping 生成:用 `tools/bfs-crawl/android_viewtree_bfs_crawler.ts` 采集 Android 运行时 viewtree dump,再转换为 `ui_elements.json`。

核心问题不是「把场景写成操作步骤」——这不难;难在**让写出来的用例值不值得信**。TCG 的解法是「生成与复核分离」+ 四诚实证据 + 两道复核门。

---

## 2. 架构总览

三层 + 一份契约:

```
        ┌─────────────────────────────────────────────┐
        │            TCG (Orchestrator skill)          │
        │  瘦壳:S0 建表 / S1 分批 / S6 收口 + 驱动循环 │
        │  自己不读 SPEC 正文 / 不派生 / 不判语义      │
        └─────────────────────────────────────────────┘
                           │ 逐批:dispatch generator → validate.ts → validator
                           ▼
   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
   │  generator   │ → │  validate.ts │ → │  validator   │
   │  生成(不自核)│   │  机械核(S4) │   │  语义复核(S5)│
   └──────────────┘   └──────────────┘   └──────────────┘
   ←──── FAIL 重试 ≤3 次(各门独立)────→
                           │
        ┌─────────────────────────────────────────────┐
        │  共享底座:contract.md(三方共读)             │
        │  四诚实 + 7 硬约束 + 用例格式                 │
        └─────────────────────────────────────────────┘
```

**形态**:1 skill + 2 agent + 3 reference + 2 tool,没有更多。思路是**能用程序就不开 agent、单读者的纪律不单独抽 reference**。

**两条铁判据(不可违背):**
1. **可判定边界**——S4(程序 `validate.ts`)与 S5(`validator` agent)分属两侧,机械核不做语义判断、语义校验不被判真,二者不可合并。
2. **生产者 ≠ 复核者**——generator 只生成不自核;S4/S5 不重做生成。

---

## 3. 各文件职责

> `SKILL.md` / `references/` / `tools/` 同属 `skills/hmos-test-case-generation/` 目录;agents 装在仓库根 `agents/` 目录(独立于 skill,由 SKILL.md 调度)。

| 文件 | 角色 | 干什么 | 不干什么 |
|------|------|--------|----------|
| `SKILL.md` | 编排器 skill | S0 建场景表 / S1 枚举+分批 / S6 收口 + 驱动逐批循环(S2+S3 generator → S4 validate.ts → S5 validator)+ 重试兜底 | 不读 SPEC 正文、不派生、不渲染用例、不做语义校验 |
| `agents/test-case-generation-generator.md` | 生成子 agent · S2+S3 | S2 沿引用有界 BFS 裁工作集(≤窗口)+ 场景内 branch 派生 + S3 按 contract §3 写四诚实证据;repair 模式只修被点名项 | 只生成不自核(不跑 validate、不做语义判断) |
| `agents/test-case-generation-validator.md` | 复核子 agent · S5 | 四类语义裁决(来源相关+预期忠实 / 语义完整 / oracle 充分 / 跨态稳定)+ 跨场景数据等价去重;每条裁决带引用 | 只裁决不生成(不重写用例) |
| `references/contract.md` | 三方共享契约 | 四诚实 + 7 硬约束(左:generator 必满足 / 右:prog 机械核 + validator 语义)+ 用例格式骨架。generator 写 / validator 读 / validate.ts 扫,三方指这一份 | 不描述编排流程(指向 SKILL.md) |
| `references/review-notes-template.md` | 人工伴随件模板 | 单一 `review_notes.md` 的结构(阻塞区在上 + 非阻塞区在下,合一为一个文件);S6 渲染套用 | — |
| `references/ui-elements-schema.md` | UI 元素表 schema | 定义 `ui_elements.json` 的结构与消费规则;S0 step 3 的 dump → convert pipeline 按此产出,generator 的信任分层按此消费 | — |
| `tools/validate.ts` | 非 LLM 程序校验 | `cases`:S4 可判定核(非空/结构/枚举/外键/计数);`md`:S6 成品复核(文件名/禁字段/单一伴随件/pre_test_case 形态);`verify`:S5 核 validator 报告引文接地(req_span 场景存在 + quote 逐字命中 SPEC/test_case);`slice-spec`:确定性切累计 SPEC 视图 | 不做语义判断(含糊/恒真/相关性归 S5),verify 只核引文真伪不核裁决对错 |
| `tools/convert_to_ui_elements.ts` | 非 LLM 转换器 | viewtree dump → `ui_elements.json`(S0 step 3 调用,zero-LLM,详见 §8) | 不参与校验、不做语义判断 |

> **single source of truth**:派生与证据规则都在 `contract.md`;编排流程只在 `SKILL.md`;agent 只指向、不另抄;改 contract §3(格式/字段/枚举)须同步 validate.ts 顶部代码镜像正则。

---

## 4. 数据流与中间产物

### 输入项

| 输入 | 必需 | 说明 |
|---|---|---|
| `spec-path` | 是 | `<feature>-SPEC.md`(spec-generate 产出,`## 场景N` 原子场景块)。意图 ≈ 场景:领取已拆好的场景,不再自行发现意图。 |
| `ui-elements-path` | 否 | `ui_elements.json`。**软参照**:不保真、可缺。缺但给了 `package` 或 `android-project-dir` 时,TCG 在 S0 step 3 自动产(BFS dump → convert);三者都缺则相关软核跳过。 |
| `package` | 否 | Android 应用包名(如 `com.example.app`)。`ui-elements-path` 缺时用它直接跑 dump,优先于 `android-project-dir`(跳过推导)。需 Node.js ≥22.18 / 23.6、ADB + 设备。 |
| `android-project-dir` | 否 | Android 源码工程根目录。`ui-elements-path` 和 `package` 都缺时才用:从 `build.gradle` 的 `applicationId` 推导包名 → 跑 dump。 |
| `references-dir` | 否 | 外部参照包:操作定义/页面描述/模板/可复用前置用例库/特殊测试数据。软参照,缺则相关复用机会消失、流程照走。 |
| `output-path` | 否 | 默认 `<sibling of spec-path>/testcase-output/`。 |

### 主要交付产物

| 输出 | 必需 | 产生步 | 说明 |
|---|---|---|---|
| `test_case.md` | 是 | S3 逐批写 | 主用例,自带四诚实证据,SKIP 记录也写在此 |
| `pre_test_case.md` | 条件必须 | S3 累计 / S6 收口 | generator 外置「见前置用例」前提,S6 去重、排序、重编号;有命中则必须产出,无命中则不出 |
| `review_notes.md` | 是 | S6 合并 | **单一**人工伴随件(阻塞区 + 非阻塞区合一;**不得另产 manual-intervention.md**,拆文件会被 `md` 复核判 `companion_not_merged` / `manual_intervention_forbidden` FAIL) |

> **目录纪律**:交付目录只放上述三件;所有过程件(`cases-report.json` / `md-report.json` / 裁决文件及 `_bfs_dump/`)写入同级 `{output-path}.work/`,不进交付目录;交付件单独可读,人工处理所需信息不依赖过程产物或本机路径。
> **文件名是强约束**:固定名 `test_case.md` 等,下游 `hmos-integration-test` 以固定名 `test_case.md` 作为 `test-case-path` 接入。

---

## 5. 怎么调用

调用 orchestrator,它会自动处理所有场景:

```
Skill(hmos-test-case-generation, args: `
  spec-path: path/to/<feature>-SPEC.md        # 必需
  ui-elements-path: path/to/ui_elements.json  # 可选,软参照;缺则见下行
  package: com.example.app                    # 可选,直接传包名(优先于下行)
  android-project-dir: path/to/android/proj   # 可选,package 缺时从工程推导包名
  references-dir: path/to/references/         # 可选
  output-path: path/to/testcase-output/       # 可选,默认 spec 同级
`)
```

完成时 stdout 最后一行:

```
TCG_COMPLETE specs={N} ok={K} failed={A} output={output-path}
```
(N=场景数,K=两门均放行的场景数,A=留记号/FATAL 的场景数)

ABORT(S4/S5 退出码 3 全局致命):
```
TCG_ABORT reason=<全局配置致命> output={output-path}
```

---

## 6. 编排流程 S0–S6(概要)

完整规则见 `SKILL.md`。要点:

- **S0 建场景表**:读 SPEC,grep `## 场景N` 建索引(只切 span,不深读);探测软参照可读性;建输出目录。
- **S1 领场景 + 分批**:每个 `## 场景N` 领为一份意图(漏领 FAIL);按上下文窗口贪心装箱(锚点 `CTX × 0.5`)。
- **逐批循环**(换页边界 = 一批):
  - **S2+S3** dispatch generator(取信息 + 生成四诚实证据,只生成不自核)
  - **S4** 覆盖刷新“截至当前批”的单个累计 SPEC 视图,再由 `validate.ts cases` 对累计 test_case 做程序可判定核(退出码 0/1/2/3)
  - **S5** dispatch validator(四类语义裁决 + 去重);报告按批次/尝试留档,并用 `reviewed_scenarios` 做本批覆盖回执
  - 两门各独立 ≤3 次重试(有效 FAIL 同页重开、只修被点名项;S5 坏报告只重写报告);耗尽留记号交人、本批不停。
- **S6 全局收口**:totality 终扫(防静默蒸发)+ 全量去重终扫(每场景去重后 ≥1)+ 合成三件交付件 + 可选 `md` 成品复核 + 红线声明 + 收口信号。

> 每批最坏:1×generate + ≤3×S4-repair + ≤3×S5-repair = ≤7 次 generator 派发 + ≤12 次 validate.ts 调用(7 × `cases` + 4 × `verify` + 1 × `slice-spec`,含 S5 修复后的 S4 回归检查)+ ≤4 次 validator。

---

## 7. 四诚实 + 控制不变量(概要)

完整定义见 `contract.md`。

**四诚实**(契约的为什么):有源(对得上 SPEC)/ 周延(每支有去向)/ 可循(动作落到具体对象)/ 可证(TP 是机器能判真假的二值状态)。做不到的写带原因的 `[SKIP]`,不假装做到。

**7 条硬约束**(左:generator 必满足 / 右:prog 机械核 + validator 语义):
1. 有源·场景映射;2. 周延·场景覆盖;3. 可循·动作具体;4. 可证·TP 非平凡;5. 兜底·SKIP 规范;6. 去重·base 撞车不丢;7. 前置·标注合法。

**推导类型**:`[推导]` 表示非 base、从同一 SPEC 场景内部派生。generator 在 `## 场景来源映射` 的 delta 中标注推导类型,validator 独立核对分类与分支覆盖;类型闭集及处理规则以 `contract.md` §3.3 为准。

**控制不变量**:每意图必有去向(出用例 / 出 SKIP / 留记号);场景 totality(每场景 ≥1 用例);ui_elements 非阻塞软核(硬门只留文本自洽 + SPEC 外键);按上下文窗口粗估换页;重试耗尽不扔、留记号交人。

> **签名构成**以 `contract.md` §2 #6 为唯一定义;validate.ts 据此判逐字精确重复。数据等价(100 vs 101、歌单A vs B)归 validator 语义侧,程序不抹平。

---

## 8. viewtree pipeline(产出 ui_elements.json)

`ui_elements.json` 是 TCG 的可选软参照输入(§4)。当 `ui-elements-path` 缺省但 `package` 或 `android-project-dir` 给了时,TCG 在 **S0 step 3** 自动跑这个两步 pipeline(runtime BFS view-tree dump → TCG schema)产 `ui_elements.json`;给了 `ui-elements-path` 则跳过。产物结构与消费规则见 `references/ui-elements-schema.md`。

```
Step 0  BFS dump    tools/bfs-crawl/android_viewtree_bfs_crawler.ts (Node + ADB)
  → page_NNNN_<Activity>/{meta.json, view.xml, screenshot.png, view_scroll_*.xml}
Step 1   convert    convert_to_ui_elements.ts       (Node ≥22.18 / 23.6, zero-LLM)
  → ui_elements.json + page.parsed.json (display_texts 副作用)
```

### Step 0: BFS dump

BFS crawler:

```powershell
node .\tools\bfs-crawl\android_viewtree_bfs_crawler.ts `
  --package com.example.app `
  --output .\bfs_output `
  [--device emulator-5554] [--max-depth 6] [--max-pages 500] [--max-scrolls 10] [--skip-same-activity]
```

BFS 机制:uiautomator dump → tap 触发导航 → 签名去重判新页 → 滚动采 display_texts。crawler 包含 root 恢复、深度上限、危险操作拦截、近似重复合并和 `gaps.json`/`crawl_stats.json`/`crawl_decisions.jsonl` 诊断信息。`--allow-pm-clear` 是显式 opt-in:只有确认允许清空 app 数据时才传。诊断信息是人工 supplement,不写入最终 `ui_elements.json`;转换器仍只消费通用 dump contract。

### Step 1: convert

```powershell
node .\skills\hmos-test-case-generation\tools\convert_to_ui_elements.ts `
  --dump <bfs_dump_dir> --out <out_dir>/ui_elements.json `
  [--app-name <name>] [--package <pkg>] [--description <desc>]
```

### 测试(mock 数据,无需设备)

```powershell
cd skills/hmos-test-case-generation/tools
node convert_to_ui_elements.ts --dump test_fixtures --out test_out/ui_elements.json
```

---

## 9. validate.ts(程序校验)

非 LLM 的 TypeScript 脚本,四子命令(`cases` / `md` / `verify` / `slice-spec`)。靠 Node 原生类型擦除运行(Node ≥22.18 / 23.6 直接 `node validate.ts`),无需编译、无依赖。

```bash
# S4:校验 test_case.md 用例结构(可判定核)
node tools/validate.ts cases <test_case.md> --spec <spec.md> [--ui <ui.json>] --report <report.json>

# S6:校验交付件成品
node tools/validate.ts md <output_dir> --report <report.json>

# S5:核 validator 报告引文接地(req_span 场景存在 + quote 逐字命中 SPEC/test_case)
node tools/validate.ts verify <validator-report.json> --spec <spec.md> [--test-case <test_case.md>] --report <report.json>

# S4:确定性切累计 SPEC 视图(SPEC 前言 + 指定场景块,滚动校验视图)
node tools/validate.ts slice-spec <spec.md> --scenes 1,2,3 --out <out_dir>/spec-through-current-batch.md
```

退出码:`0` 过 / `1` 规则失败(可重试,吐 report)/ `2` 不可解析(本批/目录级 FATAL)/ `3` 配置缺失(全局 FATAL)。

校验失败写 report.json,含 `failed_items[]`(每项有 `rule` + `scenario_id` + `detail` + `fix_hint`),由编排器喂回 generator 做定向修复。**程序只查存在/计数/枚举/外键,不做语义判断**(含糊/恒真/相关性/数据等价归 S5)。

---

## 10. 常见问题

**Q: 为什么校验用程序而不是让 LLM 自检?**
A: 字面包含、文件命名、禁用字段、外键这类硬约束,程序判定 100% 可靠,LLM 自检会漏。LLM 只负责需要语义理解的部分(含糊/恒真/相关性/数据等价)。

**Q: 一个场景失败会影响其他场景吗?**
A: 不会。退出码 2(本批 FATAL)和重试耗尽都只跳过当前批,留记号交人。只有退出码 3(全局配置缺失)才中止整条流水。

**Q: 重试会不会死循环?**
A: 不会。S4、S5 各独立 MAX_RETRY=3,达到上限即标记失败放行(留记号交人),不再重试。

**Q: 为什么把人工伴随件合成一个 review_notes.md?**
A: 阻塞区(须先处理才能跑)和非阻塞区(不挡执行)分区但合一为一个文件,避免拆文件后人只看一份漏了另一份。validate.ts `md` 会拦「另起 manual-intervention.md」和「多个伴随件并存」。
