AI 写代码的「修一炸三」老毛病,被我自己造的一个 skill 治了

发布于 阅读 2278未分类

先说痛:AI 写代码最让我睡不着觉的副作用

这个博客的前端、后端、运维、数据库备份、部署脚本全是我个人业余时间维护。我只出需求、出方向、代码全是我让 AI 写的

最近一年我大量用 AI 帮我改后端。说实话,写起来是真的爽:

  • 想加个"管理员能改任意文章"权限?一句话的事,三秒给答案;
  • 改某个接口的字段校验?一句话的事;
  • 加个错误码?一句话的事;
  • 修个"数据库找不到这条数据"没翻译的错误?一句话的事。 但是,AI 改完经常留下"小尾巴" ,这些尾巴拼起来就成大问题:
  1. 修一个问题,炸出三个新问题。 经典的"我给管理员开了个免校验的绿色通道,结果普通用户也跟着被放行了";经典的"我把空的列表从 null 改成了 [],但 [] 又被一个老判断当成 '有数据' 走了另一条路"。
  2. 新问题不是立刻爆,是过好几个阶段才爆。 比如改了一下文章相关的业务代码,炸的却是评论列表;比如改了下接口的注册顺序,炸的是后台某个我几乎不打开的页面。
  3. 甚至过了几周我让 AI 写别的功能,才发现"之前好好的地方突然发现不能用了",可距我上次提交代码已经过了半个月之久了。
  4. 我没空挨个接口都人肉点一遍。 一个人写的小网站,业余时间维护,改完部署上去就不管了,习惯了这种"上线即祈祷"的状态。

所以我需要一套自己能在本地一键跑、能复现、能看错在哪的 e2e 测试。不是为了覆盖率,是为了"敢改"


之前用程序跑测试的痛点

在说 skill 之前,先说我之前怎么测:

之前的方案痛点
Postman 手点一个接口点 50 遍之后眼瞎;token 过期又得重登;改一个 case 不敢确认其他 case 还活着
自己写 Python 脚本没规范,想到哪写到哪;跑完一次就扔;换台电脑要重配环境;前后置数据完全靠"我记得"
用现成测试框架(httpx + pytest)配测试数据文件一大堆,跑得慢;不熟悉的同事接手直接劝退;我一个人维护没动力写这么多
让 AI 直接生成测试脚本它会"为了绿而绿":把 expect 改成跟 bug 一样的返回值,让我以为修好了。其实什么都没验
curl + jq 随手测测一个能行,测一个半的,测一个全忘了;下次想回归"上次哪儿改了",根本找不到历史

光说 Postman 这条,值得单独多讲两句。我之前是真的用 Postman 当开发工具使的:

每个接口我都"像开发的时候一样",手点 N 遍 —— 增加或减少几个可选字段、增加或减少必选字段、使用正确和错误的数据类型、长度等,反复对一个接口测。

每点一次心里就在嘀咕"这次应该测什么"。点完一遍,我把 Postman 那 N 种尝试全部导出成 curl,想着以后总得写成脚本测试吧。

结果发现根本不可行:

  • 导出的 curl 一坨参数,过两天回来看完全不知道在测什么
  • 改一个接口的字段,这一堆 curl 全废
  • 想"复用"的时候,得一条条对着重新跑,等于又点一遍
  • 那个 curl 文件夹最后变成了电子垃圾桶 —— 收了一堆请求,啥也跑不起来

最致命的是"为了全过而全过"。 改完一个问题,再让 AI 改测试,AI 看一眼"哦原来是期望 200",直接把 50000 改成 200,你就得到一份"全过但其实没验任何东西"的报告。

这几种方案,都解决不了"一个人 + 业余时间 + 持续维护"这个真实场景。


为什么用 skill

Skill 是"角色插件":我写一份 markdown 告诉它"你是测试编排员,你怎么跑、怎么判断、怎么出报告",然后用一个 /api-test 命令就能召唤它。

我选 skill 路线,是因为它长在我已经习惯的工作流里:

  • 我本来就每天都要打开IDE或Claude Code
  • 它能直接读 cases.yaml、跑 run.sh、看 report.jsonl、告诉我"第几条为什么没过"
  • 它不会被"为了全过而全过"骗到——因为 skill 文件里写死了硬规则,比如"不修改后端代码"、"不破坏 state.json"、"失败信息要带原始请求和返回"
  • 我可以用"前一次跑出来的失败"作为上下文,让 AI 帮我只改后端、不动测试,反推出问题在哪

换句话说:skill 把"测试怎么跑"和"测试怎么写"这两件事都变成可版本控制的纯文本。我一个人也能 hold 住。


为什么是 skill,不是 pytest

我不是没试过 e2e 测试。去年就折腾过一轮,目标很朴素:

启一个空数据库,自动注册一个账号 → 登录拿到登录凭证 → 带这个凭证去增加各种数据,再测所有接口的增删改查。

听起来简单。实际做起来,几个卡点每个都致命:

  • 注册→登录→拿凭证的链路 —— 每一步得先跑通才能进下一步,失败一次后面全白搭
  • 接口之间有依赖关系 —— 要测发文章,得先有分类和标签;要测评论,得先有文章;要测删分类,得先有一个用着它的文章。几十个接口拧成一根链,顺序错一个就崩
  • 每个接口还得测反例 —— 错密码、过期凭证、不存在的 ID、跨用户权限、字段超长,排列组合一上来就成百上千条
  • 测试脚本最后自己就是个 DSL —— setup → 主流程 → 反例 → 清理,自己写完自己都晕

去年我让 AI 帮我"整理"这个测试脚本。AI 听完,讲了一套 pytest 教科书:fixture、conftest、parametrize、hook、marker……头头是道。我照着写,很快就放弃了。

问题在哪?AI 给的方案是 "教科书正确",但在我这个具体项目里就是跑不起来。要么 fixture 互相引用打死结,要么参数化跟我的接口形态对不上,要么脚本顺序和业务顺序冲突,要么某个 hook 吃掉我的错误码。

AI 嘴上头头是道,跑起来一塌糊涂。

这事我放下了。直到最近两天,我一直在想一个问题:能不能让 AI 不再"给我写测试",而是"AI 亲自当测试员"? 不是 AI 给我一段脚本我去跑,而是 AI 自己跑测试、看到红色自己知道怎么办。

脑洞就来了:skill 机制。把"测试怎么跑、跑出来怎么判断、错了怎么办"全写在一个 markdown 文件里,我每次输 /api-test,AI 就被召唤成"测试编排员",按这份文件干活,亲自跑、亲自看、亲自跟我说"这条红的可能是 X"。

这就是 api-test skill 的诞生。

skill 凭什么能跑通我之前跑不通的

跟 pytest 比,skill 没多出啥"超能力"。根本差别就一句话:AI 不再是"给我一段脚本我去跑",而是"AI 自己在跑测试"

具体表现就是,之前那几个卡点一个个被解掉:

之前的卡点pytest 那套skill 模式
怎么启空环境我得自己写 docker-compose、写清理脚本skill 里有 mode.reset_db 一行开关,清表 + 重迁 + 初始化管理员一条龙
注册→登录→拿凭证我得自己写一长串 setup 函数,失败回滚还得手写YAML 里写 auth: u1 就完事,AI 自己去找之前用例保存的 token
接口之间的顺序我自己排 fixture 依赖,排着排着就乱AI 看着 cases.yaml 帮我排;我加一条用例,AI 自己挑阶段、挑位置、决定要不要先建前置数据
每个接口的反例我自己 parametrize 一通,组合爆炸我对 AI 说"加测 X 的边界",AI 自己在 YAML 里写 5 条反例
脚本顺序我自己晕测试代码写到几百行就劝退YAML 几百行,排版好,我自己一眼能看完
跑挂了谁解释我自己读堆栈、查日志、看堆AI 直接读报告,告诉我"这条红的可能是 X 字段没赋值"
怎么增量跑(只跑改动的)我得自己写 marker、配置 pytest我直接说"先只跑文章相关的",AI 改 cases.yaml 加个 filter,跑完恢复

最值钱的一行,是最后那一行:测试代码几百行,我自己看就晕;YAML 几百行,我一眼能看完。 skill 不让 AI 替我"写测试",它让 AI 替我"跑测试 + 解读结果 + 提醒我哪里要改后端"。

对我这种"一个人 + 业余时间 + 主要让 AI 写代码"的人来说,这是质的区别: 我不用额外抽时间"维护测试",测试是被 AI 改后端的那个 AI 顺手维护的。


第一次跑通:证明思路可行

最先的版本非常朴素,目标就是一句话: "返回的业务码对就算过。"

  • 跑一个 bash test/run.sh
  • 它做这些事:编译后端 → 启一个独立端口(8081,跟开发的 8080 隔开)→ 等服务正常启动 → 清空所有数据表(管理员账户保留)→ 跑用例 → 出一份网页报告 → 关掉后端
  • 每个用例写在一个 YAML 文件里,带 expect.code(期望业务码)和 expect.msg_contains(期望错误信息里要包含的那句话)
  • 跑出来的卡片直接列出"我发了什么请求 / 后端回了什么 / 业务码是多少",没过的话一目了然

第一版我让 AI 写了一百多个主流程用例。大概 100 个左右,核心 11 个阶段:环境就绪、注册、登录、当前用户、分类、标签、文章增删改查、评论、单页面、上传、错误处理。

写完一跑,100% 全过。这其实一点都不奇怪——我是按"应有行为"写的期望,如果按"迁就现状"写,那 100% 是廉价的。我特意没迁就,就是想看看真实后端会暴露什么。

这次跑通证明了一件事:这个思路行得通。 一键能跑、能复现、能出报告。哪怕只有 100 个用例,也比"改完部署上线就靠运气"强 100 倍。

下面这张图是整个工作流的全貌,后面会按这张图一个个展开讲:

(图:AI 改后端 → 触发 /api-test → 自动跑测试 → 看红绿报告 → 红的让 AI 修,绿的安心部署;以及 4 个阶段严格度一路加码)


第一阶段:让 AI 写测试,覆盖所有接口

跑通之后,我做了一件"反直觉"的事:让 AI 帮我写更多 case。

为什么反直觉?因为按我之前的经验,AI 写测试不可靠。

但这次不一样——skill 里写死了一条硬规矩: "测试不主动改后端代码" 。也就是说,AI 写测试时,它没法靠改后端来"实现"它的期望。它只能老老实实写"应该有",然后跑出来没过的话,问题归后端管。

所以我让 AI 一口气把后端所有接口全覆盖了。结果:

  • 11 个阶段 → 不变
  • 用例总数从 100 涨到 217
  • 一跑出来,立刻暴露出后端一堆问题(后面会讲)

这一阶段的核心:返回的业务码正确就算过code: 200 算过,code: 50000 算挂,msg_contains 是模糊匹配一下错误信息里有没有指定的字。

最直接的成果:我把后端 60 多个接口的"应有业务码"全部钉死了。再没人(包括我自己)能用"我以为这个返 200"来糊弄过去。


第二阶段:响应的结构也得对

跑了一阵,发现一个尴尬事:业务码对了,数据是错的,测试也显示通过。

比如:

  • GET /users/currentcode: 200,但 data.user.username 不是 user1,是 usernme(AI 改字段名时拼错);
  • POST /articlescode: 200,但 data.article.id 是字符串而不是数字;
  • GET /tagscode: 200,但 data 整个是 null,因为空表时空切片被序列化成了 null。

业务码说"过",其实过的是空壳

所以加了 expect.data 这个字段:

yaml
expect:
  code: 200
  data:
    type: object
    fields:
      - path: $.data.token
        type: string
        not_empty: true
      - path: $.data.user.username
        type: string
        eq: user1
      - path: $.data.tags
        type: array
        min_len: 1

规则很轻量:type(类型) / eq(等于) / oneof(在某个白名单里) / contains(包含) / not_empty(非空) / min_len(最少多少) / max_len(最多多少) / matches(正则匹配),fields 一层层下钻。

我让 AI 把现有 217 个用例全部补上 expect.data,然后跑。

结果:通过率从 100% 直接掉到 84.8%(184/217)。

没过的那 33 个,全是真问题。最经典的几个:

  • 空切片被序列化成 null(前端每个数据读取的钩子都要加 ?? [] 兜底)
  • "数据库找不到这条数据"没翻译,直接 50000
  • 字段名拼写错误

这一阶段的"红利"是:风格债被钉死了。 "序列化一致性"从一个"大家默认遵守"的口头约定,变成了测试报告里明明白白的是/否。从此再没人能"忘了"加 ?? []


第三阶段:字段类型也得对

加了 expect.data 之后,马上又发现一个层次的问题。

假设:测试用例写的是"等于 1",后端返的也是 1,显示通过。但按 JSON 接口规范,****1 应该是数字,不能是字符串

而代码那边稍不留神就会把数字序列化成 "1"(比如某些字段被设成字符串,或者经过文本格式化)。

所以又加了 type: number 强制约束,光值一样还不够,类型也得对

类似的还有:

  • data.user.role 必须是 "user""admin" 之一,我加 oneof
  • data.created_at 必须匹配日期格式,我加 matches(正则)
  • data.articles 是数组时,每个元素都得有 id/title/status 三个字段,我加 array_items

这一轮又是暴雷。AI 改完几个用例之后,又冒出几个"业务码对 + 结构对 + 单字段值对 + 字段类型错"的微妙问题。


第四阶段:副作用产生的数据量也得对

到这里,我以为差不多了。

然后有一天我好奇:"我注册了两个用户,登录拿登录凭证,写了一篇文章,加了三条评论,删了一篇文章——列表里应该有 2 个用户、1 篇文章、3 条评论,数据量真的对吗? "

我加了一组列表接口的"最少多少 / 最多多少"断言,要求"我刚刚操作完,列表长度就该是多少"。

结果立刻不过。原因五花八门:

  • 删除一个用户,关联的评论没连带删,列表里残留"幽灵评论"
  • 被标记删除的文章还出现在公开列表里(假删没真删干净)
  • 翻页参数 ?page=0 真的返第 0 页(2 条),而不是回退到第 1 页
  • ?limit=0 真的返 0 条(空响应),而不是用默认值
  • 被标记删除的实体在关联查询里被算两次

这一阶段的"红利"最大:测试开始验证"业务流程的副作用",而不只是"单次接口的返回值" 。它把"我能不能信任这个后端"从"单个接口看起来对"升级到"业务流程对得上"。


跑完报告,我又顺手让 AI 把 bug 修了

到这里你以为流程结束了?其实真正的闭环在报告跑完之后

测试报告一出来,我直接对 AI 说一句: "把报告里红的那几条,逐条帮我看看 —— 是不是真 bug,如果是,直接改后端,改完跟我说改了哪些文件。"

然后 AI 会按这几步走:

  1. 逐条读红的用例 —— 看报告里的 request / response / 业务码,判断到底是谁的锅
  2. 区分"测试期望有误"和"后端真有 bug" —— 如果是前者,AI 主动停下来跟我确认;"是不是期望写错了?"
  3. 确认是后端 bug → AI 只改后端代码,绝不碰 cases.yaml(这一条是写死在 skill 里的硬规矩)
  4. 改完给我看 diff —— 改了哪几个文件、哪几行,一句话讲清楚改了什么

我做什么?只看 diff,审一眼改得合不合理、有没有把测试代码偷偷改了、修改范围有没有扩大。 没问题,一句"提交",AI 帮我生成提交信息,推上去。

这是我能两天修 30 多个 bug 的真正原因 —— 不是熬夜,是"修"这个动作也被 AI 顺手自动化了。 我在前面写"反向促进 API 完善"那一节里列的 4 次修复提交,基本就是这个流程跑出来的:

  • 跑一次测试 → 报告出来 N 条红的
  • 让 AI 修 → AI 改完给我 diff
  • 我审 → 通过
  • 再跑一次测试 → 报告里少一些红,多了几个新红(也可能是 AI 改出来的)
  • 继续让 AI 修
  • 直到全过

这个循环完全在我下班前的 1~2 个小时里完成,每次循环大概 20~40 分钟。我做的实际工作只剩两件:审 diff + 偶尔判断"这条是不是测试期望写错了,而不是后端的锅" 。剩下全是 AI 在干。

这种"测试 + 修 bug"打包在一起的工作流,在我之前的方案里是完全不可能的:Postman 时代我得自己看返回、人肉定位、自己改代码、自己 commit;pytest 时代我得自己读堆栈、自己定位、自己改代码。skill 把它打包成一个对话回合,我只需要在最关键的两个判断点(期望有误 vs 后端 bug + diff 是否合理)露脸。


反向促进 API 完善:这两天我修了多少个 bug

写到这里,真正核心的问题来了:AI 帮我改问题容易,AI 帮我写测试反过来逼我修问题,反而是更值钱的产出。

我把这两天的版本记录翻了一遍(只统计后端的代码改动),给自己也算笔账:

整体规模

  • 6 次提交,18 个后端源文件被改过
  • 新增代码 4500 多行,删除 1700 多行
  • 4 次是修复,1 次是结构重构,1 次是新增功能(测试模式)

按提交拆,4 次修复一共解决了 30 多个具体问题

第 1 次修复(深夜) 涵盖:

  • "数据库找不到这条数据"统一识别(影响查分类/标签/文章详情等多个接口)
  • 接口注册顺序调整(静态接口 /top 不再被动态的 /:id 抢走)
  • 文章修改增加管理员绿色通道,与删除对齐
  • 文章访问量排行排序字段名 views 改成 view_count(之前一直是 500)
  • 标签删除前先检查存不存在 + "正在被 N 篇文章使用"统一错误码
  • 用户不存在统一错误码
  • 401 鉴权失败改用统一响应格式(不再裸 HTTP 码)
  • 搜索的全文索引在 SQLite/MySQL 行为差异
  • 已知错误文案自动替换脚本
  • {{N 个字符}} 占位符支持(测最大长度边界)
  • run.sh 跨平台关进程 + 文件路径兼容

第 2 次修复(上午) 涵盖:

  • 全局把空切片从 null 改成 [](前面提到的"风格债")
  • 分类/标签创建拒绝纯空白名
  • 注册时检查邮箱是否重复
  • GET /users/current 改用登录凭证上下文拿 ID(原本写死了 1,会撞死)
  • 验证码测试模式可绕过
  • 请求结构补字段长度校验:description 最长 200、content 最长 500、guest_name 最长 50
  • 业务校验错误和服务器错误分开(不再全 50000)
  • 错误码文案统一中文

第 3 次修复(上午晚些) 涵盖:

  • 登录按错误信息返回对应错误码(密码错和用户不存在分开)
  • 新增"被封禁"状态检查,被封禁用户拒绝登录
  • 文章排序字段 views 改成 view_count
  • cases.yaml 一次扩 408 行,补用户状态/业务流程

第 1 次重构(凌晨) 涵盖:

  • 新建 BaseModel 替代老的 gorm.Model + 全部 model 加 json 标签(序列化字段名统一)
  • 序列化时空切片不再变 null
  • expect.data 断言支持(对应前面第二阶段)
  • 网页报告强化 data 校验展示
  • 忽略文件调整

几个让我印象深的"测出来的问题"

我永远不会主动想到的测试怎么揪出来的
删一个用户,关联评论没连带删阶段 13 加"最少多少"之后,数字对不上
老的 gorm.Model 字段没 json 标签,JSON 返大写 key我以为只要业务码对就过了,加"类型是对象"验字段名,立刻炸
?page=0 真的返第 0 页(2 条)阶段 11 加"翻页边界",按"应有"写,后端没回退
注册新用户时,邮箱是否重复完全没查加"等于 user1@test.local"验新注册,验完发现邮箱能撞
被封禁用户还能用旧登录凭证继续请求阶段 13 加"登录凭证边界",封禁后用旧凭证仍 200
view_count 字段名写错,排行接口一直 500期望业务码 200 的用例不过,改期望之前先抓响应发现是 500
data: null 反复出现一加"类型是数组",空表场景 100% 不过

这其中有至少 3 个是"修问题改出来的问题", 我自己永远不会回头去测:接口注册顺序、字段名拼写、假删的关联查询行为。没有这套测试,我到下次自己撞上才会发现。


Skill 自己也不是银弹:几个我自己也头疼的痛点

聊完好处,得诚实说说 skill 现在的局限:

1. AI 写测试还是会"为了全过而全过"。 我得在 skill 里反复写硬规则提醒它"不许改期望来迁就现状",它偶尔还是会忘。唯一保险的办法是我自己回头看。 我现在每加一组用例,跑一次,看没过的那批是不是真的在抓问题,而不是"全过但其实没验"。

2. 跨阶段顺序敏感。 我有一条用例是"删使用中的分类,期望 3003",但只有 art1 引用了 tech 之后,这条用例才能拿到 3003;如果 art1 还没建,它就返 200。我的"应有"写法和实际跑出来的状态对不上,就得在 note: 里写明"前置依赖 art1 已建"。这种"隐性知识"藏在 YAML 的顺序里,新人接手根本看不出来。

3. state.json 是会咬人的。 跑完"改密码"那组用例,u1 旧登录凭证还活着,后续依赖 u1 旧密码的用例直接被跳掉一片。我 debug 半天才发现。所以"先按生命周期、最后跑破坏性的用例"是硬规则,但这条规则没强约束,只能靠我自律

4. 上传、限流、MySQL 特性测不到。 文件上传要准备测试数据文件、限流要调白名单、MySQL 的全文索引 / 事务隔离 / 字符集我用 SQLite 跑不到。这些边界我心里有数,但 skill 目前覆盖不了。

5. 持续集成没接上。 提一句,因为我用的编译镜像没装 sqlite3,跑测试要改服务配置 / 镜像选择 / 阶段顺序 / 与编译并行,得另开一仗。


现在它长什么样

写完这篇的时候,我手头的最终状态:

  • 300 个用例 / 14 个阶段
  • 100% 都带 expect.data 断言(报告里 "data 校验" 一栏是 300/300)
  • 跑了 91 次,1 秒级完成
  • 终端 + 网页双报告,网页版有粘性顶部可以跳阶段、按关键字搜

cases.yaml 里有个 note: 字段专门记"现状/已修",目前 34 条。这 34 条是测试帮我"看见"的后端缺陷清单,每修一条划一条。

Bash
# 一键跑
$ bash test/run.sh
[build] 编译后端到 test/bin/server
[server] 启动 (port 8081)
[ready] 等后端就绪 OK (500ms)
[clean]7 张表
[run] test/run_tests.py
[report] test/build_html_report.py --real
[done] exit=0

  ✓  环境就绪           2/2
  ✓  用户注册          13/13
  ...
  300 总数   300 ✓ 通过   0 ✗ 失败   0 ? 跳过
  整体通过率 100.0%      用时 1.0s

写在最后

这套东西不是一次写完的。我分了 4 个阶段,每阶段要求都更严:

  1. 业务码对就算过(217 个用例)
  2. 响应的结构也得对(引入 expect.data,通过率掉到 84.8%)
  3. 字段类型也得对(数组元素、白名单、正则)
  4. 副作用产生的数据量也得对(列表长度、连带删、假删)

每加一层,测试就逼着我修一批问题。这两天 4 次修复提交、30 多个修复点,就是这个过程的产出。

最大的收获不是"300 个用例全过",是"今天我敢放心让 AI 改后端"。 改一行,跑一遍;没过的让 AI 改,过了再部署。这种安心,业余时间维护个人项目的人最懂。

由于这个skill测试用时很短,几秒就完成,以后我甚至可以在修改这个项目的时候,提交前把这个skill跑一遍,没问题再提交。

如果你也是一个人写后端,被 AI 写代码的速度爽到、被它留下的问题吓到,试试这套思路:让 AI 帮你写测试,而不是让 AI 改你的期望。 跑出来没过的归后端管,过了的归期望管。测试永远比后端诚实,这是它值钱的地方。


附录:关键数据

指标数值
用例总数300(初版 100,扩到 217,再扩到 300)
阶段总数14(初版 11,扩阶段 11/12/13)
响应数据断言覆盖100%
历史报告91 份网页报告(累积保留)
两天内的接口改动6 次提交,18 个后端文件,新增 4500 多行 / 删除 1700 多行
修过的问题点数30 多个(4 次修复提交 + 1 次结构重构)
cases.yamlnote: 字段(已知现状)34 条
跨平台Linux / WSL / Git Bash on Windows
一键命令bash test/run.sh(或在 AI 编程助手里输 /api-test)
评论 (0)
0 / 500
暂无评论,快来抢沙发~