两周前,我在禅道上提了一个 Bug。开发者修完了,MR 合并了,按流程我该写回归用例了。

按惯例应该是:打开飞书文档 → 重新理解需求 → 打开 GitLab 看 diff → 翻历史 Bug → 打开禅道写用例 → 填写标题、步骤、预期 → 回填关联需求 ID。一套下来,这个回归用例至少半小时。

这次不一样。我打开了 Hermes 终端,敲了一行:

1
帮我生成测试用例,需求在飞书 XXX,MR 在 XXX

然后 Hermes Agent 开始了它的工作:读飞书文档、拉 GitLab diff、查历史缺陷、分析代码风险信号、写测试点、生成详细用例、写入禅道、回填 ID。

3 分钟不到,30 条相关的测试用例完美地躺在禅道里,P1/P2/P3 分布合理,覆盖正向/异常/边界/UI/幂等五个维度。其中有 11 条用例的测试角度来自代码 diff 分析——需求文档里根本没写这些边界。

这不是工具安利。这是一次关于「如何把工程思维应用到 AI 工作流设计」的系统性实验记录。

1. 痛点:一个 QA 的工具切换日常

我日常使用的工具链并不复杂:

  • 禅道 — 测试用例、Bug、任务的唯一数据源
  • GitLab — 前后端代码仓库,MR diff 里藏着真正的变更细节
  • 飞书 — 需求文档和 Wiki 知识库
  • zread — 代码文档辅助工具

每个需求测试的完整链路是:飞书读需求 → 本地读 GitLab diff 理解变更 → 禅道查历史同类 Bug → 设计测试点 → 写用例 → 手动录入禅道。

这个流程最大的问题是:所有上下文切换和信息筛选都依赖人的经验和记忆力。需求文档写了 80% 的逻辑,剩下 20% 的边界和异常藏在 MR diff 里。读漏了就是漏测。

另一个问题是质量一致性。同一个团队里,不同人写的测试用例风格差异很大:有的写「验证功能正常」这种无法判断的预期,有的把步骤写成技术接口描述而非业务操作,有的忘了覆盖边界场景。不是态度问题——是缺少一个统一的「用例质量护栏」。

2. 方案设计:结构、技能与工作流

2.1. 设计思路:为什么是三阶段

一开始很容易想到的架构是「一条 prompt 搞定」——给 AI 所有上下文,让它直接输出禅道格式的用例。试过,效果不行。问题不在于 AI 理解不了需求,而在于没有纠错环节的单次生成,质量方差太大

我换了一个思路:把测试过程拆成知识理解 → 方案设计 → 执行录入三段,每段产出独立、可审查、可修正。

1
2
3
4
5
6
7
8
9
10
11
阶段一:知识归档(理解)
核心目标:在动手写用例之前,确保 AI 充分理解了「要测什么」
包含:读领域知识库、读需求文档、读代码 diff、查历史缺陷

阶段二:测试用例归档(设计)
核心目标:先出测试点概览,再出详细用例,两层审查
包含:写测试点汇总表 → 等确认 → 写详细用例 → 等确认

阶段三:写入禅道(执行)
核心目标:将确认后的用例写入数据源,并保持本地与远端一致
包含:逐条创建 → 回填 ID → 抽查验证

这个拆分有三个好处。第一,知识归档和用例编写分离,避免 AI 在信息不全的情况下强行输出。第二,每个阶段结束时都有一个确认点,人可以介入纠偏——这在 AI 工作流中极其重要,因为 AI 在阶段一犯的错,到阶段三才发现,回退成本很高。第三,阶段的边界也是 skill 的边界——后续测试点改模板、换工具链、改知识库结构,影响范围是可控的。

2.2. 项目结构

测试用例的产出不是一个孤立动作,它依赖知识库、模板、历史记录。我把这些组织成一个项目目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
allInOne/
├── README.md # 项目总览
├── 工作流/
│ └── testcase-pipeline.md # 8 步执行流程(唯一流程真相)
├── 模板集/
│ ├── TEMPLATE-需求.md # 需求归档模板
│ ├── TEMPLATE-测试点.md # 测试点汇总表模板
│ └── TEMPLATE-测试用例集.md # 详细用例模板(唯一格式真相)
├── 需求集/ # 每次归档一条 .md
├── 测试点/ # 测试点汇总表
├── 测试用例集/ # 详细用例,上传后回填禅道 ID
└── knowledge/
├── _index.md # 顶层导航
├── epvs/ # ePVS 领域知识(术语/流程/视图/计算规范)
│ ├── 01-术语定义/ # 25+ 个正式术语
│ ├── 02-业务流程/ # 采集流程、告警判断、爬产更新
│ ├── 03-UI视图/ # 各页面说明
│ ├── 04-计算规范/ # 指标口径与计算差异
│ ├── 05-架构说明/ # 模块依赖与数据模型
│ └── 06-测试场景/ # 异常链路与并发场景
├── epvs-runtime/ # 运行时命名空间/服务索引
└── canonical-node/ # 知识编写规范与模板

这里有一个关键设计:knowledge 目录作为 AI-first 知识库。每个术语页都有统一的 frontmatter 元数据,用 Obsidian 的 Multi Properties 插件维护,AI 检索时按 _index.md 导航快速定位。业务术语(如 Stand Alone TP、Baseline、Bottleneck)不是散落在需求文档里,而是集中在知识库中,随时可查。

2.3. Skill 体系:把工程约束编码为 AI 可执行的规程

Hermes Agent 的核心机制是 skill——相当于 AI 的「可执行规程」。我构建了三个核心 skill:

① test-case-writing(测试用例编写护栏)

这个 skill 不做「怎么写用例」的教程——教程放在工作流和模板里。它只做两件事:执行纪律红线提醒

执行纪律包括:写用例前必须先读工作流文件、必须按三阶段执行、每阶段完成后等确认。红线提醒包括:不测防御性 guard clause、不测编译时保证、不测业务不可达状态、不写纯技术 API 参数校验类用例。

它还有一个阶段二自检清单——每条用例在提交确认前,检查步骤数是否等于预期数、是否违反有效性红线、前置条件是否完整、步骤是否可执行、预期是否可观察。

② testcase-pipeline(8 步工作流)

对应三阶段八步的完整执行流程,定义了每一步的输入输出和确认点。包含风险评分规则(按改动文件数、代码行数、是否含异步任务、是否有数据库变更评估 🟢/🟡/🔴),以及实战中踩过的坑(跳过阶段一直接写用例导致返工、格式参考应该看模板而非凭记忆等)。

③ zentao-cli(禅道 CLI 操作参考)

封装了 zentao-cli 的完整命令参考,包括认证方式、常用操作、明确的陷阱——比如 workspace set 不会自动注入 –product、testcase update 必须传全量字段否则会被清空、创建测试用例时 steps/expects 数组长度必须一致。这些陷阱都是真实踩过之后补上的。

这三个 skill 的协作方式是:用户说「生成测试用例」→ Hermes 加载 testcase-pipeline → 该 skill 强制要求先加载 test-case-writingtest-case-writing 强制要求先读工作流文件 → 开始执行。每层都在做「确保不被跳过」的事。

2.4. 质量护栏:从 prompt 到工程约束

这个体系中最反直觉的设计是:工作流和模板是文件,不是 skill 的一部分

skill 负责「提醒你必须读文件」,但不复制文件内容。工作流只有 工作流/testcase-pipeline.md 一份,模板只有 模板集/ 下的三个文件。如果某天需要改流程,只改一个文件;需要改格式,只改对应的模板文件。skill 不需要更新。

这会牺牲一些便捷性——每次执行前都要读一遍文件。但保证了当流程或模板变化时,不会出现「改了 A 忘了改 B」的多源冲突。对于长期维护的系统来说,单一真相来源比便捷性重要得多。

3. 第一次跑通:Auto Baseline 需求

第一个真实跑通的需求是「Auto Baseline 自动更新任务」。一个涉及保存系数、任务管理、分配任务、执行日志、恢复回滚的前后端协同功能。前端 MR 5726 行 diff,后端涉及三个微服务。

30 条用例最终写入禅道(#1782~#1810),覆盖六个模块:

  • 保存系数(3 条)
  • 新建/管理自动更新任务(5 条)
  • 分配自动更新任务(4 条)
  • 自动更新执行/日志/恢复(6 条)
  • 前端 UI/交互(7 条)
  • 后端 API/边界(4 条)

有趣的数据是:30 条用例中,11 条的来源标注为「代码特性」而非「需求文档」。具体来说:

代码信号 对应测试点 需求文档是否覆盖
closeOnOutsideClick=false 测试 ESC 和外部点击关闭弹窗
hasNegativeValue() 测试负数输入阻断
executionLogRestoreThrottleMs=500 测试双击节流
@Valid 参数校验 测试服务端必填参数校验

这些边界条件不是 AI 凭空生成的——它读了 MR diff,看到代码里写了这些分支,然后判断「这是正常用户能触发的操作路径吗?」如果是,就转化为测试用例。

第一版我生成了 15 条用例——步骤模糊、预期不可判断、缺少边界。第二版按质量标准重写后变成 30 条。AI 第一次产出的质量并不能直接交付,但迭代成本远低于从零开始写。

4. 第二次跑通:一个真实 Bug 的回归验证

第二个测试是 Bug #1457 的回归。问题很具体:Event Timeline 页面中,用户从其他页面选了班次后切过来,按钮一直显示班次编号(如「1.7」),选完自定义时间后按钮文案仍然停留在「1.7」,造成认知冲突。

MR !423 改了四个东西:

  1. 按钮标签从智能缩写改为始终显示完整日期格式
  2. 弹窗改为响应式缩放(按视口等比缩小,最小 0.35 倍)
  3. DateBox 下拉改为挂到 document.body,避免小弹窗内 overflow 裁切
  4. 自定义 closeOnOutsideClick,防止点日历被当成「外侧点击」关掉弹窗

最终产出 14 条用例,覆盖按钮格式(同天/跨月/跨年/取消回退)、弹窗响应式(正常/小屏/极屏/resize)、点击行为(日历/遮罩/内容)、布局兼容性四个模块,全部写入禅道 #1828~#1841。

这轮测试验证了一个事情:流程对 Bug 回归场景同样有效。不需要飞书文档,直接从禅道取 Bug → 读 MR diff → 查历史 → 归档 → 写用例 → 上传,全链路可跑。

5. 意外收获

流程设计没有问题,但工具调用层面的细节让我印象更深。

5.1. 错误一:前置条件没传上去

14 条用例全部写入成功,标题、步骤、预期都正确。但禅道的「前置条件」字段全是空的。

排查过程:从禅道查 testcase <id> --pick=precondition,返回空字符串。回到代码看——precondition 字符串里含 \n(换行符),在 f-string 拼 shell 命令时变成了实际换行,zentao-cli 的参数解析器在换行处截断了参数。

教训:传给 CLI 的参数值不能带换行符。用中文分号「;」代替 \n。这个细节后来写进了 test-case-writing 的 lessons-learned 里,下次执行前自动加载。

5.2. 错误二:回填 ID 时 replace_all 翻车

14 条用例写入成功后需要回填禅道 ID。用了 replace_all=true——结果所有 14 条的「禅道ID:未上传」都被替换成了同一个 ID「#1828」。

教训:批量替换用行号逐条赋值,不用全局替换。

5.3. 错误三:步骤数和预期数不一致

阶段二自检时发现,有几条用例的测试步骤数量与预期结果数量不一致。多一步或少一步,上传时 steps 和 expects 数组长度不匹配。

教训:预期结果用有序列表(1. 2. 3.)和步骤编号对齐,是结构层面的预防手段。

这三个错误都不是流程设计问题,都发生在「AI 调用外部工具的边界」上——如何正确构造 shell 命令、如何做批量文件操作、如何保证结构化数据的长度一致性。AI 工具链的成熟度,很大程度上取决于这些边界细节的处理。

6. 现状与下一步

目前的体系完整架构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
用户输入(禅道任务 / MR 链接 / 飞书文档)


Hermes Agent 加载 skill
├── testcase-pipeline ── 8 步工作流的执行引擎
├── test-case-writing ── 执行纪律 + 红线护栏
└── zentao-cli ──────── CLI 操作参考(含陷阱标注)


allInOne 项目文件
├── 工作流/ ────── 流程定义(唯一真相)
├── 模板集/ ────── 格式定义(唯一真相)
├── 知识库/ ────── 领域知识(epvs / runtime / canonical)
├── 需求集/ ────── 每次任务的归档记录
├── 测试点/ ────── 汇总表
└── 测试用例集/ ─── 详细用例 + 禅道 ID


外部工具
├── zentao-cli ───────── 用例数据源
├── GitLab-cli ─────── 代码和 MR
└── feishu-cli ───────── 需求文档

它的能力边界是:输入一个禅道任务或 MR 链接,能独立跑完从知识归档到写入禅道的完整链路。两次真实验证都跑通了,产出直接可用。

最后,附上testcase-pipeline.md文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# 测试用例生成工作流

## 触发条件

你给一个需求模板文件或直接提供需求链接 + MR 链接。

## 三阶段流程

> 严格按顺序执行:**知识归档 → 测试用例归档 → 写入禅道**。每完成一阶段先确认,再进入下一阶段。

> ⚠️ 模板文件(allInOne/模板集/)是项目结构定义,禁止擅自修改。任何模板改动必须先提出方案和理由,等用户确认后才能执行。

---

## 阶段一:知识归档

### Step 1: 加载领域上下文

-`allInOne/knowledge/<project>/` 读取相关领域知识
-`/Users/phoenine/Documents/qxzg/feature_check/<repo>/` 读取项目源码及 `.zread/wiki/` 中的架构文档
- 确认前置条件已就绪(飞书授权、禅道/GitLab认证)

### Step 2: 读取需求文档并更新知识库

- 通过 `lark-cli` 读取飞书文档全文
- 提取新术语、业务规则、验收标准、系统架构
- **将新知识补充到 `allInOne/knowledge/<project>/` 对应目录**
-`allInOne/模板集/TEMPLATE-需求.md` 复制模板
- 写入 `allInOne/需求集/<功能名称>.md`,归档任务链接、MR/commit、代码变更摘要、领域术语、变更说明
- 产出:知识库已反映当前任务的最新上下文 + 需求集已归档

### Step 3: 读取代码变更

- **优先读取本地**:进入 `feature_check/<repo>/`,先 `git pull` 拉取最新代码,再 `git show <commit>` 查看 diff
- **本地无此项目时**:通过 `glab` 从 GitLab 克隆到 `feature_check/`
- 提取关键变更:新增API、逻辑分支、校验规则、UI交互
- 识别风险信号:改动文件数、新增代码量、技术栈变更

### Step 4: 关联历史缺陷

- 通过 `zentao bug --product=ID --filter='status:closed' --limit=10` 拉取近期关闭的 Bug
- 按关键词匹配本次需求相关的历史缺陷
- 如果匹配到,在测试用例中额外补充该类缺陷场景

### Step 4b: 检查已有用例覆盖

- 搜索 `allInOne/测试用例集/` 中是否已有覆盖本次场景的用例
- 如果已有覆盖,说明当前是回归验证或类似场景的变体,避免重复生成
- 将覆盖结果写入需求集归档

### ✅ 阶段一确认点

产出:`allInOne/knowledge/<project>/` 已更新 + `allInOne/需求集/<功能名称>.md` 已归档 + 任务理解已清晰

**等你确认后再进入阶段二。**

---

## 阶段二:测试用例归档

### Step 5: 生成测试点

-`allInOne/模板集/TEMPLATE-测试点.md` 复制模板
- 写入 `allInOne/测试点/`,格式为汇总表(#/标题/Pri/覆盖类型)

### Step 6: 生成详细测试用例

-`allInOne/模板集/TEMPLATE-测试用例集.md` 复制模板
- 写入 `allInOne/测试用例集/`,按每条用例的完整格式(优先级/禅道ID/前置条件/步骤/预期/备注)

### ✅ 阶段二确认点

产出:`allInOne/测试点/` + `allInOne/测试用例集/` 已就绪

**等你确认修改后再进入阶段三。**

---

## 阶段三:写入禅道

### Step 7: 写入禅道

- 先读取 `allInOne/测试用例集/<功能名称>.md`,检查每条 TC 的 `禅道ID`
- 禅道ID 为 `未上传` → 执行 `zentao testcase create`
- 禅道ID 已有数字 → 跳过,避免重复上传
- 确认后逐条执行

### Step 8: 更新禅道ID

- 写入成功后,将禅道返回的 ID 回填到测试用例文件中对应 TC 的 `禅道ID` 字段
- 回填后可用 `zentao testcase --product=11 --search=<关键词> --pick=id,title,pri` 抽查确认,不拉全量 JSON
- 目的:禅道ID 字段是本地与禅道用例的对应关系,防止重复上传同一批 case


回头看,这个实验的核心收获不是「AI 能写测试用例」——现在大家都能让 AI 写文本。核心收获是:当 AI 被嵌入一个有明确阶段划分、有质量护栏、有回退机制的工作流时,它的产出才是可交付的

没有这些约束,AI 产出 15 条质量不达标的用例只需要 2 分钟。有了这些约束,AI 产出 30 条可直接上禅道的用例也只要 3 分钟——多出来的 1 分钟花在读取知识库、分析代码 diff、自检和修正上。这是用工程思维让 AI 从「快速产出废物」变成「慢速产出可用品」的关键一步。

AI 不会替代测试人员。但会有一批测试人员,用 AI 重新定义自己的工作流。