Skill: fdingiit/gen-test (version 1.0.0)

--- name: test-gen description: >- 为已有项目生成单元测试和 E2E 测试。分析设计文档和函数签名（黑盒）， 评审可测试性设计缺陷，生成测试代码并产出覆盖率报告。 Use when: "generate tests", "add tests", "补测试", "生成单测", "write unit tests", "add e2e tests", "测试覆盖率"。 Proactively suggest when user has an existing project with insufficient test coverage and asks about quality or stability. disable-model-invocation: true --- # Test Gen: 为已有项目生成测试 ## Overview 本 skill 面向**代码已存在、需要补充测试**的场景（区别于 TDD 先写测试再写代码）。 通过五个阶段，从项目理解到测试交付，系统性地为已有项目生成高质量测试。 **核心理念：** - **黑盒优先** — 从函数签名和接口契约出发，不偷看实现 - **诚实评审** — 设计有问题就直接说，不生成低质量测试来掩盖 - **覆盖率可量化** — 每次生成后产出覆盖率数据作为质量基线 ## 安装 支持两种安装方式，使用 AskQuestion 让用户选择： **项目级安装**（推荐协作项目，跟随 git 提交，团队共享）： ```bash mkdir -p .cursor/skills && cp -r /path/to/test-gen .cursor/skills/test-gen # 同时安装斜杠命令 mkdir -p .cursor/commands && cp /path/to/test-gen.md .cursor/commands/test-gen.md ``` **用户级安装**（个人全局使用，跨项目可用）： ```bash mkdir -p ~/.cursor/skills && cp -r /path/to/test-gen ~/.cursor/skills/test-gen ``` > 项目级安装的 skill 优先于用户级。如果两处都存在，项目级生效。 > 用户级安装时不支持斜杠命令（命令只能放在项目的 `.cursor/commands/` 下）。 ## 前置检测 在开始前，自动检测项目环境： ``` 检测清单： 1. 语言 — 扫描文件后缀和配置文件（package.json / go.mod / pyproject.toml / pom.xml / Cargo.toml 等） 2. 测试框架 — 检查已有依赖（jest / vitest / pytest / go test / JUnit / RSpec 等） 3. 已有测试 — 搜索 *_test.* / *.test.* / *.spec.* / test_*.* 文件 4. 覆盖率工具 — 检查是否已配置（nyc / c8 / coverage.py / go cover 等） 5. 设计文档 — 搜索 README / ARCHITECTURE / docs/ / DESIGN.md 6. 构建系统 — 检查 Makefile / Taskfile / justfile 是否存在 7. VCS 状态 — 检查 git 是否可用（用于后续变更行覆盖率） ``` ### 自动安装缺失的覆盖率工具 如果检测到项目**有测试框架但没有覆盖率工具**，自动安装： | 语言 | 条件 | 安装命令 | |------|------|----------| | Python | 有 pytest 但无 pytest-cov | `pip install pytest-cov` | | JS/TS (Jest) | 无 --coverage 配置 | 已内置，无需安装 | | JS/TS (Vitest) | 无 @vitest/coverage-* | `npm install -D @vitest/coverage-v8` | | Go | — | 内置 `go test -cover`，无需安装 | | Rust | 无 cargo-tarpaulin/llvm-cov | `cargo install cargo-llvm-cov` | | Java (Maven) | 无 JaCoCo 插件 | 在 pom.xml 中添加 JaCoCo 插件配置 | | Java (Gradle) | 无 jacoco 插件 | 在 build.gradle 中添加 `id 'jacoco'` | | C#/.NET | 无 coverlet | `dotnet add package coverlet.collector` | 安装前使用 AskQuestion 确认： ``` 检测到项目缺少覆盖率工具。建议安装： - [工具名] (安装命令: `xxx`) 是否立即安装？ ``` 详细的安装配方见 [coverage-recipes.md](coverage-recipes.md)。 将检测结果汇报给用户，确认无误后进入覆盖率阈值加载。 --- ## 覆盖率阈值配置 在前置检测完成后、进入 Phase 1 之前，加载覆盖率阈值。 ### 阈值定义 三层阈值，各自独立判定： ```yaml coverage_thresholds: overall: # 全项目总体阈值 line: 80 # 行覆盖率 % branch: 70 # 分支覆盖率 % function: 80 # 函数覆盖率 % per_file: # 单文件阈值（任何单文件不低于此值） line: 60 branch: 50 delta: # 变更行覆盖率阈值 line: 90 # 新增/修改的代码行覆盖率 ``` ### 配置来源优先级 按顺序查找，找到即停： 1. **项目配置文件** `.test-gen.yaml`（项目根目录） ```yaml coverage_thresholds: overall: line: 85 branch: 75 function: 85 per_file: line: 70 branch: 60 delta: line: 95 ``` 2. **项目包管理配置中的嵌入字段** - Python `pyproject.toml`：`[tool.test-gen.coverage_thresholds]` - Node.js `package.json`：`"test-gen": { "coverage_thresholds": { ... } }` 3. **默认值**（上方定义的 80/70/80、60/50、90） 加载后向用户展示当前生效的阈值及来源。 ### 阈值在流程中的使用 - **Phase 4 步骤 4**（全量覆盖率）：将实际值与 `overall` + `per_file` 阈值逐项对比 - **Phase 4 步骤 5**（变更行覆盖率）：将实际值与 `delta` 阈值对比 - 将阈值传递给底层工具的 `--fail-under` 参数（如工具支持） - 未达标指标在报告中标注 `!! NO` 并给出与目标的差距 --- ## Phase 1: 设计文档分析 **目标**：理解项目的设计意图、模块划分和数据流。 ### 步骤 1. **搜索设计文档** - 优先级：ARCHITECTURE.md > DESIGN.md > README.md > docs/ 目录 > 代码注释头 - 也检查：API 文档（OpenAPI/Swagger）、proto 文件、GraphQL schema 2. **提取关键信息** - 项目目标和核心功能 - 模块/包划分及职责 - 核心数据流（输入 → 处理 → 输出） - 外部依赖（数据库、第三方 API、消息队列等） - 预期的行为约束和业务规则 3. **无文档时的降级策略** - 从目录结构推断模块划分 - 从 import/require 关系推断依赖图 - 从 package/module 配置推断项目类型 - 从已有测试推断预期行为 4. **产出项目理解摘要** 格式： ```markdown ## 项目理解摘要 ### 项目类型 [Web 应用 / API 服务 / CLI 工具 / 库 / ...] ### 模块划分 | 模块 | 职责 | 核心文件 | |------|------|----------| ### 核心数据流 [描述主要数据流向] ### 外部依赖 [列出所有外部依赖及其用途] ### 业务规则与约束 [从文档中提取的关键业务规则] ``` 详细指南见 [design-analysis-guide.md](design-analysis-guide.md)。 ### STOP — 与用户确认 使用 AskQuestion 向用户展示项目理解摘要，确认： - 模块划分是否准确 - 是否遗漏了关键模块或业务规则 - 测试范围的优先级（哪些模块最需要测试） 用户确认后进入 Phase 2。 --- ## Phase 2: 函数签名黑盒分析 **目标**：从公开 API 的角度理解每个函数的契约，不看实现。 ### 铁律 ``` 禁止读取函数体。只看签名、类型定义、文档注释。 ``` 违反这条规则就失去了黑盒视角，测试会不自觉地追随实现细节。 ### 步骤 1. **识别公开 API** - 扫描导出的函数、类、方法 - 排除内部/私有符号（_前缀、unexported、private 等） - 关注 Phase 1 中用户标记为高优先级的模块 2. **提取签名信息**（仅以下内容） - 函数名 - 参数名和类型 - 返回值类型 - 关联的类型/接口定义（struct、interface、type alias） - 函数上方的文档注释（doc comment） - 是否为 async / 是否抛异常 3. **推断测试维度** 对每个函数，基于签名推断： | 维度 | 说明 | |------|------| | 正常输入 | 典型合法输入 | | 边界值 | 空值、零值、最大/最小值、空集合 | | 异常输入 | 类型不匹配、nil/null/undefined、非法格式 | | 异常路径 | 基于返回的 error/exception 类型推断 | | 并发安全 | 如果签名暗示并发使用（channel、mutex、sync 相关） | 4. **产出签名清单** 按模块分组，每个函数一行： ```markdown ### 模块: [name] | 函数 | 参数 | 返回值 | 推断的测试维度 | |------|------|--------|----------------| | CreateUser | (name string, email string) | (User, error) | 正常/空字符串/重复email/非法格式 | ``` --- ## Phase 3: 可测试性评审 **目标**：判断每个函数是否适合直接编写测试，不适合的直接指出设计缺陷。 这是本 skill 的**关键差异化阶段**。大多数测试生成工具会为所有函数无脑生成测试， 导致大量 mock 堆砌和脆弱测试。本阶段的职责是**诚实评估**。 ### 评审维度 详细清单和示例见 [testability-checklist.md](testability-checklist.md)。 核心维度： | 维度 | 好的设计 | 坏的设计 | |------|----------|----------| | 纯度 | 相同输入 → 相同输出 | 依赖全局状态或隐式输入 | | 依赖注入 | 外部依赖通过参数传入 | 函数内部硬编码 new/connect | | 接口隔离 | 参数各自独立，职责清晰 | God Object 参数，一个 struct 传所有 | | 确定性 | 不依赖时间、随机数 | 内部调用 time.Now() 或 Math.random() | | 可观测性 | 行为可通过返回值验证 | 关键行为只通过副作用发生 | | 函数粒度 | 单一职责，逻辑聚焦 | 一个函数做五件事 | ### 三种判定 对每个函数给出判定： **可测 (TESTABLE)** - 可以直接生成高质量测试 - 进入 Phase 4 **需重构 (NEEDS_REFACTOR)** - 指出具体设计缺陷 - 给出建议的重构方向（如"将 DB 连接抽为接口参数"） - **不自动重构** — 这超出了本 skill 的职责范围 - 不为这类函数生成测试 **跳过 (SKIP)** - 说明原因：trivial getter/setter、纯委托/胶水代码、已有充分测试 - 不生成测试 ### 产出 ```markdown ## 可测试性报告 ### 统计 - 可测: X 个函数 - 需重构: Y 个函数 - 跳过: Z 个函数 ### 需重构的函数 #### [函数名] - **问题**: [具体的设计缺陷] - **影响**: [为什么这会导致测试困难] - **建议**: [具体的重构方向] ### 跳过的函数 | 函数 | 跳过原因 | |------|----------| ``` ### STOP — 与用户确认 使用 AskQuestion 展示可测试性报告： - 用户确认"需重构"的判定是否合理 - 用户决定是否先处理重构再继续 - 用户可以覆盖判定（强制为某个函数生成测试） --- ## Phase 4: 单元测试生成 + 覆盖率 **目标**：为所有"可测"函数生成高质量单元测试，并产出覆盖率报告。 ### 测试设计原则 1. **一个测试一个行为** — 测试名即文档 2. **命名规范**：`test_<行为>_when_<条件>_should_<预期>` 3. **等价类划分**：正常输入 / 边界值 / 异常输入 4. **真实对象优先** — mock 仅用于不可控的外部依赖（网络、DB、文件系统） 5. **断言明确** — 每个测试有且仅有一个核心断言 6. **测试隔离** — 测试之间无顺序依赖，无共享可变状态 ### 步骤 1. **确定测试文件位置** - 遵循项目已有的测试文件组织惯例 - 如无惯例，同目录 `*_test.*` 或 `__tests__/` 目录 2. **按模块生成测试** - 对 Phase 2 签名清单中每个"可测"函数： - 生成正常路径测试（至少 1 个） - 生成边界值测试（基于 Phase 2 推断的边界条件） - 生成异常路径测试（基于 Phase 2 推断的异常路径） - 使用项目已有的测试框架和断言库 3. **运行测试** - 执行全部新生成的测试 - 全部通过则继续 - 如有失败：分析失败原因，修复测试代码（不修改被测代码） - 如失败源于被测代码 bug — 报告给用户，保留失败测试作为 bug 文档 4. **生成覆盖率报告** 使用项目对应的覆盖率工具（参见 [coverage-recipes.md](coverage-recipes.md)）。 产出摘要（阈值从覆盖率阈值配置中读取）： ```markdown ## 覆盖率报告 ### 总体覆盖率 | 指标 | 覆盖率 | 目标 | 达标 | |------|--------|------|------| | 行覆盖率 | XX.X% | 80% | YES / !! NO (-X.X%) | | 分支覆盖率 | XX.X% | 70% | YES / !! NO (-X.X%) | | 函数覆盖率 | XX.X% | 80% | YES / !! NO (-X.X%) | ### 单文件覆盖率（未达标文件） | 文件 | 行覆盖率 | 目标 | 差距 | |------|----------|------|------| | [仅列出低于 per_file 阈值的文件] | ### 未覆盖的关键路径 [列出仍未覆盖的重要代码路径] ``` 5. **变更行覆盖率（Delta Coverage）** 除全量覆盖率外，计算**仅针对本次变更代码**的覆盖率。 这对于"给已有项目补测试"的场景尤为重要 — 关注新增测试是否覆盖了目标代码。 步骤： - 使用 `git diff` 获取相对于基准分支（main/master）的变更行 - 将覆盖率数据与变更行做交集 - 产出变更行覆盖率摘要 详细工具配方见 [coverage-recipes.md](coverage-recipes.md) 中的"变更行覆盖率"章节。 追加到覆盖率报告中（阈值从 `delta` 配置读取）： ```markdown ### 变更行覆盖率（Delta Coverage） **基准分支**: main **变更文件数**: N **变更行数**: M **目标**: 90% | 文件 | 变更行 | 已覆盖 | 覆盖率 | 达标 | |------|--------|--------|--------|------| **总变更行覆盖率**: XX.X% (目标: 90%) — YES / !! NO ``` 6. **生成 Makefile targets（如项目使用 Makefile）** 如果前置检测发现项目有 `Makefile`，生成测试相关 make targets （`test`, `test-unit`, `test-e2e`, `test-cov`, `test-cov-delta`, `test-cov-html`）。 生成规则： - 检查 Makefile 中是否已有同名 target，有则跳过 - 命令内容根据项目语言和测试框架填充（模板见 [coverage-recipes.md](coverage-recipes.md)） - 覆盖率阈值作为 Makefile 变量传入（如 `COV_LINE ?= 80`） - 使用 AskQuestion 确认后再写入 Makefile ### STOP — 与用户确认 使用 AskQuestion 展示： - 测试运行结果 - 全量覆盖率报告 + 变更行覆盖率报告（含阈值达标状态） - 未达标项的具体差距和建议 - Makefile targets（如已生成） - 是否继续生成 E2E 测试 --- ## Phase 5: E2E 测试生成 **目标**：基于用户故事和数据流，生成端到端集成测试。 ### 步骤 1. **识别关键用户路径** 基于 Phase 1 的项目理解，识别核心 E2E 场景： | 项目类型 | 典型路径 | |----------|----------| | Web 应用 | 用户注册 → 登录 → 核心操作 → 验证结果 | | API 服务 | 认证 → CRUD 操作 → 状态验证 → 错误处理 | | CLI 工具 | 参数解析 → 执行 → stdout/stderr 验证 → 退出码 | | 库 | 公开 API 的集成调用链 | 2. **选择 E2E 框架** | 项目类型 | 推荐框架 | |----------|----------| | Web 前端 | Playwright（首选）/ Cypress | | API 服务 (Node) | supertest | | API 服务 (Go) | net/http/httptest | | API 服务 (Python) | httpx + pytest | | CLI 工具 | 子进程调用 + 断言 stdout/stderr/exit code | 3. **生成 E2E 测试** - 每个测试覆盖一条完整的用户路径 - 包含 setup（准备测试数据/环境）和 teardown（清理） - 测试数据与测试代码分离（fixture 文件或 factory 函数） - 处理异步操作（等待、重试、超时） 4. **运行 E2E 测试** - 执行全部 E2E 测试 - 如需启动服务，提示用户或使用项目已有的启动脚本 - 报告结果 ### 产出 ```markdown ## E2E 测试报告 ### 测试场景 | 场景 | 覆盖的用户路径 | 状态 | |------|----------------|------| ### 测试数据管理 [描述 setup/teardown 策略] ``` --- ## 输出规范 ### 测试代码质量标准 生成的测试代码必须满足： - **可独立运行** — 不依赖其他测试的执行顺序或副作用 - **失败信息清晰** — 断言失败时能定位到具体的预期 vs 实际差异 - **无硬编码路径** — 使用相对路径或环境变量 - **无 sleep/delay** — 使用轮询或事件等待代替固定延迟 - **符合项目代码风格** — lint 规则、命名惯例与项目一致 ### 文件组织 遵循项目已有惯例。如无惯例：单元测试同目录 `*_test.*`，E2E 测试放 `e2e/` 目录。 --- ## 异常处理 | 情况 | 处理方式 | |------|----------| | 无设计文档 | Phase 1 降级为目录结构分析，明确告知用户信息来源 | | 无类型信息（纯 JS/Python） | Phase 2 从 doc comment 和变量名推断类型 | | 函数全部"需重构" | 暂停流程，给用户一份完整的重构建议清单 | | 测试框架未安装 | 提示安装命令，等用户确认后继续 | | 覆盖率工具未安装 | 自动检测并提示安装（见前置检测章节），用户确认后安装 | | 测试发现被测代码 bug | 保留失败测试，在报告中标记为"疑似 bug" | | 非 git 仓库 | 跳过变更行覆盖率，仅产出全量覆盖率 | | Makefile 已有同名 target | 跳过该 target，不覆盖已有配置 | | 无 .test-gen.yaml 配置 | 使用默认阈值（80/70/80、60/50、90），告知用户可创建配置文件自定义 | | 覆盖率未达标 | 报告中标注差距，不阻塞流程，由用户决定是否补充测试 |

path: testability-checklist.md
content: # 可测试性评审清单

本文档是 Phase 3（可测试性评审）的详细参考。对每个函数逐项检查，给出判定。

---

## 评审维度详解

### 1. 纯度 (Purity)

**定义**：相同输入是否总是产生相同输出，且无可观测的副作用。

| 级别 | 特征 | 测试难度 |
|------|------|----------|
| 纯函数 | 无副作用，输出完全由输入决定 | 最易测试 |
| 可控副作用 | 有副作用但通过参数注入依赖 | 可测试 |
| 隐式副作用 | 内部直接操作全局状态、文件、网络 | 难以测试 |

**好的设计**

```typescript
function calculateDiscount(price: number, tier: CustomerTier): number {
  // 纯计算，无副作用
}
```

**坏的设计**

```typescript
function calculateDiscount(userId: string): number {
  const user = db.query(`SELECT * FROM users WHERE id = ?`, userId); // 隐式 DB 依赖
  const config = globalConfig.discountRates; // 全局状态依赖
  logService.info(`Calculating discount for ${userId}`); // 副作用
  // ...
}
```

**判定标准**：
- 函数签名中没有任何表示外部依赖注入的参数，但函数名暗示需要外部数据 → 可能有隐式依赖 → 标记 NEEDS_REFACTOR
- 签名为 `(primitives) → primitives` 且无 async → 大概率为纯函数 → TESTABLE

---

### 2. 依赖注入 (Dependency Injection)

**定义**：外部依赖（DB、HTTP client、文件系统、时钟等）是否通过参数/构造函数传入。

**好的设计**

```go
type UserService struct {
    repo UserRepository  // 接口，可替换为 mock
    clock Clock          // 接口，可控制时间
}

func (s *UserService) CreateUser(ctx context.Context, req CreateUserReq) (*User, error) {
    // repo 和 clock 都可以在测试中替换
}
```

**坏的设计**

```go
func CreateUser(ctx context.Context, req CreateUserReq) (*User, error) {
    db := database.GetConnection()  // 硬编码获取数据库连接
    now := time.Now()               // 不可控的时间
    // ...
}
```

**判定标准**：
- 构造函数/工厂接受接口参数 → TESTABLE
- 函数内部使用全局单例或硬编码 `new` → NEEDS_REFACTOR
- 建议重构方向：将依赖抽为接口，通过参数注入

---

### 3. 接口隔离 (Interface Segregation)

**定义**：参数是否职责清晰，而非传入过度耦合的大对象。

**好的设计**

```python
def send_notification(recipient_email: str, subject: str, body: str) -> bool:
    # 只接收需要的数据
```

**坏的设计**

```python
def send_notification(context: AppContext) -> bool:
    # AppContext 包含数百个字段，函数只用其中 3 个
    # 测试时必须构造整个 AppContext
```

**判定标准**：
- 参数为具体的业务值或小接口 → TESTABLE
- 参数为 God Object（字段数 > 10 且函数只用少数） → NEEDS_REFACTOR
- 建议重构方向：提取函数实际需要的最小参数集

---

### 4. 确定性 (Determinism)

**定义**：函数是否依赖不可控的外部状态（时间、随机数、环境变量等）。

**常见的不确定性来源**

| 来源 | 问题 | 解决方案 |
|------|------|----------|
| `time.Now()` / `Date.now()` | 每次运行结果不同 | 注入 Clock 接口 |
| `Math.random()` / `rand` | 不可重现 | 注入 seed 或 RNG 接口 |
| 环境变量 | 环境依赖 | 通过参数传入配置 |
| 文件系统路径 | 路径依赖 | 使用临时目录或内存 FS |
| 网络调用 | 外部服务不可控 | 注入 HTTP client 接口 |

**判定标准**：
- 签名中有 Clock/RNG/Config 接口参数 → TESTABLE
- 函数名含 `now` / `random` / `uuid` 且无注入参数 → 可能不确定 → 需进一步检查
- 确认不确定后 → NEEDS_REFACTOR

---

### 5. 可观测性 (Observability)

**定义**：函数的关键行为是否可通过返回值或明确的输出通道验证。

**好的设计**

```typescript
function processOrder(order: Order): ProcessResult {
  return { status: 'completed', total: 42.0, notifications: ['email_sent'] };
  // 所有结果通过返回值可观测
}
```

**坏的设计**

```typescript
function processOrder(order: Order): void {
  // 内部发送邮件、更新数据库、写日志
  // 调用者无法得知发生了什么
}
```

**判定标准**：
- 返回有意义的值或 error → TESTABLE
- 返回 void/None 且函数名暗示有副作用 → 可能不可观测 → NEEDS_REFACTOR
- 建议重构方向：返回结果对象，或通过事件/回调暴露行为

---

### 6. 函数粒度 (Granularity)

**定义**：函数是否只做一件事。

**判定标准**：
- 函数名含 `and` 或 `Or` → 可能职责过多
- 参数列表过长（> 5 个参数） → 可能在做多件事
- 返回类型过于复杂（返回多种不相关的数据） → 可能在做多件事
- 建议重构方向：拆分为多个单职责函数

---

## 判定决策树

```
对每个函数：

1. 是否为 trivial getter/setter 或纯委托？
   → YES: SKIP（说明原因）
   → NO: 继续

2. 函数签名是否可以推断出隐式外部依赖？
   （无依赖注入参数，但函数名暗示需要 DB/网络/文件等）
   → YES: NEEDS_REFACTOR（缺少依赖注入）
   → NO: 继续

3. 返回值是否可观测？
   （void/None 返回且名称暗示副作用）
   → 不可观测: NEEDS_REFACTOR（缺少可观测性）
   → 可观测: 继续

4. 参数是否为 God Object？
   → YES: NEEDS_REFACTOR（接口未隔离）
   → NO: 继续

5. 签名中是否有不确定性因素且无注入？
   → YES: NEEDS_REFACTOR（缺少确定性控制）
   → NO: TESTABLE
```

---

## 报告格式模板

```markdown
## 可测试性报告

### 统计
- 总计扫描: N 个函数
- 可测 (TESTABLE): X 个
- 需重构 (NEEDS_REFACTOR): Y 个
- 跳过 (SKIP): Z 个

---

### TESTABLE 函数

| 模块 | 函数 | 测试维度 |
|------|------|----------|

---

### NEEDS_REFACTOR 函数

#### [module].[functionName]

| 项目 | 详情 |
|------|------|
| 问题 | [具体的设计缺陷，引用评审维度] |
| 影响 | [为什么导致测试困难，会造成哪种低质量测试] |
| 建议 | [具体的重构方向，用代码片段说明 before/after] |
| 严重程度 | 高/中/低 |

---

### SKIP 函数

| 模块 | 函数 | 跳过原因 |
|------|------|----------|
```

---

## 常见反模式速查

| 反模式 | 症状（从签名可见） | 判定 |
|--------|---------------------|------|
| 隐藏依赖 | 无依赖参数但函数名含 save/send/fetch/query | NEEDS_REFACTOR |
| God Function | 参数 > 5 个，或名称含 and/process/handle | NEEDS_REFACTOR |
| Void 黑洞 | 返回 void 且名称暗示有重要行为 | NEEDS_REFACTOR |
| 不可控时间 | 涉及 schedule/expire/timeout 但无 Clock 参数 | NEEDS_REFACTOR |
| Stringly Typed | 大量 string 参数代替枚举/类型 | 中等风险，标注注意 |
| Boolean Trap | 多个 bool 参数，调用者无法知道含义 | 中等风险，标注注意 |


path: test-gen.md
content: ---
name: /test-gen
id: test-gen
category: Testing
description: 为当前项目生成单元测试和 E2E 测试（五阶段：文档分析 → 签名分析 → 可测试性评审 → 单测+覆盖率 → E2E）
---

为当前项目生成测试代码。读取 test-gen skill 并按五阶段流程执行。

**Input**: `/test-gen` 后可跟可选参数：

- 无参数 — 全项目扫描
- 模块名或文件路径 — 仅针对指定范围生成测试（如 `/test-gen src/auth`）
- `--unit-only` — 只执行 Phase 1-4（跳过 E2E）
- `--review-only` — 只执行 Phase 1-3（不生成代码，仅输出评审报告）
- `--delta-only` — 仅对 git 变更文件生成测试和变更行覆盖率

参数可组合：`/test-gen src/auth --unit-only`

**Steps**

1. **解析参数**

   从输入中提取：
   - `target`：模块名/文件路径/目录（可选，默认全项目）
   - `mode`：`full`（默认）/ `unit-only` / `review-only` / `delta-only`

   如果无法解析，使用 AskQuestion 让用户选择：

   ```
   请选择测试生成模式：
   - 完整模式（单测 + E2E）
   - 仅单测（跳过 E2E）
   - 仅评审（不生成代码）
   - 仅变更文件（基于 git diff）
   ```

2. **加载 test-gen skill**

   读取 test-gen skill 的 SKILL.md，按其指令执行。

   如果指定了 `target`：
   - Phase 1 的设计文档分析范围缩小到 target 相关模块
   - Phase 2 的签名扫描仅覆盖 target 中的函数
   - Phase 3-5 顺延范围缩减

3. **执行五阶段流程**

   按 test-gen skill 中定义的五阶段顺序执行：

   | 阶段 | full | unit-only | review-only | delta-only |
   |------|------|-----------|-------------|------------|
   | Phase 1: 设计文档分析 | YES | YES | YES | SKIP |
   | Phase 2: 签名黑盒分析 | YES | YES | YES | YES (仅变更文件) |
   | Phase 3: 可测试性评审 | YES | YES | YES | YES (仅变更文件) |
   | Phase 4: 单测 + 覆盖率 | YES | YES | SKIP | YES (仅变更文件) |
   | Phase 5: E2E 测试 | YES | SKIP | SKIP | SKIP |

   每个阶段的详细步骤、STOP 确认点、产出格式均遵循 SKILL.md 的定义。

4. **delta-only 模式的特殊处理**

   - 使用 `git diff --name-only main...HEAD` 获取变更文件列表
   - 过滤出源代码文件（排除测试文件、配置文件等）
   - Phase 2-4 仅针对变更文件中的函数
   - Phase 4 强调变更行覆盖率（delta coverage），全量覆盖率作为参考

5. **产出最终报告**

   汇总所有阶段的产出，格式：

   ```markdown
   # Test Gen 报告

   **模式**: [full / unit-only / review-only / delta-only]
   **范围**: [全项目 / 指定模块]
   **阈值来源**: [.test-gen.yaml / 默认值]

   ## 摘要
   - 扫描函数: N
   - 可测: X | 需重构: Y | 跳过: Z
   - 生成测试: M 个文件
   - 总体覆盖率: XX% (目标: 80%) — 达标/未达标
   - 变更行覆盖率: XX% (目标: 90%) — 达标/未达标
   - 未达标文件: K 个

   [各阶段详细报告]
   ```

**Guardrails**
- 严格按 SKILL.md 定义的五阶段执行，不跳步不改序
- Phase 2 禁止读取函数体（黑盒铁律）
- Phase 3 遇到设计问题必须报告，不生成低质量测试
- 每个 STOP 点必须与用户确认后再继续
- review-only 模式绝不修改任何文件
- delta-only 模式需要 git 可用，否则降级为 full 模式并提示


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)