用一份规范和 52 个技能,把自动化测试交给 AI 执行
从 9 月到现在,我把一套自动化测试改成由 AI Agent 执行:读需求、出测试计划、造数据、跑用例、归档证据。
两个月 422 次提交,最终收敛成四层结构。这篇讲怎么实现的。
一、整体结构
| 层 | 位置 | 职责 |
|---|---|---|
| 规范层 | AGENTS.md(1012 行) |
行为契约:能做什么、按什么顺序、什么不许做 |
| 技能层 | skills/<name>/(52 个) |
能力单元:SKILL.md + scripts/ + references/ |
| 执行层 | automation-test-framework/ |
项目配置 + 运行器 + 并发控制 |
| 沉淀层 | 案例/ docs/ |
案例、业务链路、系统清单、遗漏模式 |
四层的关系:规范约束技能,技能被调度编排,执行结果沉淀成资产,资产反过来补规范。
下面按层讲。
二、规范层:把口头要求写成可执行契约
第一版规范只有 273 行,本质是操作手册。现在 1012 行,因为它开始分层:
| 层 | 内容 |
|---|---|
| 原则 | 核心原则、操作纪律 |
| 流程 | 执行流程、审批节点、状态机 |
| 质量 | 证据管理、缺陷规范 |
| 安全 | 数据边界、红线 |
| 演化 | 记忆、版本、错误处理 |
其中流程写得最多,因为它最容易出岔子。AI 不缺能力,缺的是”下一步做什么”的确定性。
规范靠会话启动时加载,所以它必须是文件——改一个文件,下一次会话就生效。
关键是把模糊要求翻译成可判定规则:
| 没用的写法 | 可执行的写法 |
|---|---|
| 注意数据安全 | 禁止直接改测试环境数据;造数必须走专用脚本;脚本必须支持幂等重试 |
| 及时沟通 | 遇到环境异常/数据冲突/覆盖不足/执行超时,必须暂停并请求确认 |
| 保证质量 | 每条用例产出三类证据:操作截图、请求响应、环境快照 |
写成这样有个副作用:你得先真的想过,才写得出来。
三、技能层:知识比代码更值钱
52 个技能,每个是一个目录:
1 | skills/app-device-testing/ |
SKILL.md 的 front-matter 决定路由(description 写清”什么场景用它”),正文放的是踩坑得出的硬约定,不是使用说明。
举个真实例子,真机测试那个技能里写着三条:
- 读屏幕只走
uiautomator dump,不要同时开 Appium 会话。 实测两者并存会互相破坏:source超时 9 次、instrumentation crashed、截图超时,占该需求 blocked 用例的最大单一类别(11 次)。 uiautomator dump有固定成本,按次数做预算。 单次稳定 2.2 秒,开销在 uiautomator 启动,不在传输方式。所以只能减少调用次数——用缓存(写操作后失效、轮询显式fresh)。不要写”每 700ms 轮询 8 秒”这种循环,真实代价是若干次 2.2 秒。- 写断言前先 dump 真实结构。 曾按”电池健康度”关键字过滤,漏掉了文案里不含该词的”无法检测”,把已修复的行为读成失败,多跑三轮真机。
技能的价值不在脚本,在这些数字和约束。 脚本谁都能写,2.2 秒和 11 次 blocked 是跑出来的。
四、执行层:用配置描述被测端,用锁解决并发
每个被测端一个 project.json:
1 | { |
type 决定用哪个运行器(web→playwright、api→http runner、app→appium/adb、小程序→专用 e2e)。新增一个被测端,只加一个 json,不动代码。
并发是这套东西最容易崩的地方。lib/ 里有三类锁:
| 锁 | 解决什么 | 不做的后果 |
|---|---|---|
device-lock |
同一台真机不能并发 | 两个用例抢设备,互相点乱 |
requirement-lock |
同一需求不能并行执行 | 数据互相污染 |
yunxiao-lock |
CI 与本地执行互斥 | 同一环境两份配置打架 |
配套 rate-limit-retry:限流类失败按退避重试,其他失败直接抛。不区分错误类型的重试,等于把偶发问题变成常态问题。
五、沉淀层:案例只是其中一种
一开始我以为沉淀就是”把需求归档”。做了两个月才发现,沉淀至少分四类,而且价值完全不同:
| 类型 | 回答什么问题 | 粒度 | 生命周期 | 现状 |
|---|---|---|---|---|
| 需求案例 | 这个需求当时怎么测的 | 单个需求 | 短,随版本过期 | 22 个 |
| 业务操作知识 | 这条业务链路怎么运转 | 业务域 × 系统 × 操作 | 长,跨需求复用 | 45 篇 |
| 系统清单 | 有哪些端、谁依赖谁 | 系统级 | 长 | 前后端各一份 |
| 遗漏模式 | 这类需求容易漏什么 | 抽象规则 | 最长 | 起步阶段 |
案例:能重跑才算数
1 | 案例/<需求>/ |
executable/RUN.md 是关键——下次遇到同类需求能直接跑。没有证据的”通过”不算通过,因为既证明不了有效性,也没法复用。
业务链路:这才是能变成知识库的部分
案例会过期,但业务链路不会。比如”售后工单的补差金额怎么算”这种知识,换个需求、换个人,还是要用。
我按「业务域_系统_操作」组织这类文档:
1 | docs/raw-operations/ |
每篇记四件事:链路怎么走、涉及哪些接口和字段、有哪些前置状态、哪些地方容易出错。
案例和链路的关系是漏斗:
1 | 22 个需求案例 → 提炼共性 → 45 篇链路知识 → 抽成规则 → 遗漏模式 |
前两层我已经在做了,第三层(模式库)刚起步——建了目录,只沉淀了 1 条。这一步最难,因为它要求你从”这件事怎么做的”跳到”这类事容易漏什么”。
系统清单:给 Agent 一张地图
前端项目清单、后端项目清单 各一份,写清有哪些端、分别负责什么、依赖哪些服务。
作用很直接:Agent 拿到一个需求,先看它落在哪个端、涉及哪几个系统,再决定测什么。没有这张图,它只能靠猜。
一个准入原则
知识库最容易变成文档垃圾场。我加了一条:归档时必须回答”这次有没有新的业务知识?”
- 没有 → 只归档案例,不动链路文档
- 有 → 更新对应链路文档,并在案例里回指
不这样约束,半年后就会有一堆没人看、也没人敢删的文档。
六、CI:把本地链路搬到流水线
云效 YAML 和 Jenkins 跑同一个入口 ci/ci-entry.sh,这样本地和流水线不会分叉。
Jenkins 那个文件里留了两条实测注释,都是血泪:
1. 必须 deleteDir()。
Jenkins 复用工作区,git checkout -f 不会删被 .gitignore 排除的文件。不清会留下上一次的 config.json、node_modules、.appium,导致静默跑在旧配置上——结果看起来全绿,其实测的是上一个版本。
2. 配置只能通过凭据绑定注入。
不要写 CONFIG_JSON="$(cat 文件)",sh 步骤默认开 xtrace,会把变量值(含测试账号)原样打进构建日志。
这两条都不是”最佳实践”,是踩过才知道的。
七、两个月的数据
| 指标 | 9 月 | 10 月 |
|---|---|---|
| 提交 | 156 | 266 |
| 新功能 | 58 | 105 |
| 修复 | 15 | 57 |
| 文档 | 28 | 49 |
累计 52 个技能、22 个需求案例、81 份测试文档。
修复从 15 涨到 57,不是质量变差:9 月是想到什么加什么,10 月真跑起来,边角问题才暴露。修复量上涨,是系统从”能跑”到”可靠”的信号。
八、几个技术取舍
为什么规范是文件,不是代码注释?
读它的是 Agent,不是编译器。注释给人看,规范给执行者看;而且改文件不用重启。
为什么用三层锁,不用数据库事务?
锁的作用域是”设备/需求/流水线”,不是数据行。用文件锁最轻,坏了能手工删。
为什么更强调度能力评估后不接?
它会更快,但也更难定位问题、更难回滚。而现在真正的瓶颈是用例质量和数据准备,不是调度速度——瓶颈不在那儿,加能力只会加复杂度。
为什么事实源只有一份?
规范和对话里各说一套,Agent 会按对话执行,结果和文档对不上。所有约束只写在规范里,临时要求要么补进去,要么明确”仅本次有效”。
九、五条边界
- 能读 ≠ 能写:分析阶段只读,写操作要明确指令 + 确认
- 造数与业务操作分离:专用脚本、不碰真实链路、重试必须幂等
- 失败要留现场:缺证据记为未完成,不算通过
- 规范是唯一事实源:口头要求不进规范就等于不存在
- “不做”写进文件:红线放最前面,默契不算约束
十、最后
这套系统的能力上限,不取决于模型多强,取决于你把约束写得多清楚。
模型还会更强。今天写的边界明年可能不适用,但顺序不会变:先想清楚,再让它做。
文中数据来自提交历史与仓库统计,业务细节已脱敏。