1. 测试流程的老问题:为什么"人肉写用例"越来越跑不动了
做测试这行的,大概都经历过这样的场景:需求评审刚结束,开发还在画架构图,测试负责人就已经在催"用例什么时候能出"。然后你打开文档,对着需求文档一行一行抠,把"用户登录"拆成"输入正确账号密码""输入错误密码""账号为空""密码为空""账号不存在"……一个模块下来,几十条用例,写得手酸,评审的时候还被问"边界值考虑了吗""异常流覆盖了吗"。
这套流程在业务简单、迭代慢的年代是能跑的。但现在的情况是:一周一个版本,需求文档可能上午改下午又改,接口字段说加就加。你辛辛苦苦写完的用例,开发一句"这个逻辑调整了",一半要重写。更别提那些藏在代码分支里的边界条件,靠人脑去穷举,漏测几乎是必然的。
我真正开始认真思考"测试流程能不能用 Agent Skills 重构",是在连续两个版本都出现"用例写了但没覆盖到"的线上问题之后。复盘的时候发现,漏掉的那个场景其实在代码里写得很清楚——某个字段在特定状态下会返回空数组,而我们的用例只覆盖了"有数据"和"请求失败"两种情况。这不是态度问题,是人脑在处理组合爆炸时的天然短板。
Agent Skills 这个概念,说白了就是把大模型的能力拆成一个个可复用、可编排的"技能单元",每个技能负责一个明确的子任务,然后通过流程把它们串起来。放到测试场景里,就是让"读需求""生成用例""分析代码""定位缺陷"这些动作,各自有专门的技能来处理,而不是指望一个通用对话窗口包打天下。
这篇文章想聊的,就是我怎么用这套思路,把测试流程从"人肉驱动"改成"技能驱动"的。核心会围绕三件事展开:用例怎么从需求和代码里自动长出来、缺陷定位怎么从"猜"变成"查"、以及这套东西落地时踩过的那些坑。适合已经有一定测试经验、想把手上的重复劳动交给工具的同学参考,也适合刚接触 Agent 概念、想知道它在真实工程里怎么用的朋友。
2. 拆解测试动作:哪些环节适合交给 Agent Skills
在动手之前,我先做了一件事:把自己日常的测试工作完整录了一遍,从拿到需求到提交缺陷报告,一共拆出十七个动作。然后逐个判断,哪些是"规则明确、输入输出清晰"的,哪些是"依赖直觉、需要经验判断"的。这个判断很关键,因为 Agent Skills 擅长的是前者,硬塞后者只会得到一堆看似合理但没法用的结果。
2.1 三类高价值可自动化动作
第一类是信息提取与结构化。比如从需求文档里抽出"功能点""输入条件""预期结果",从接口定义里抽出"字段名""类型""必填性""取值范围"。这类动作的特点是:输入是非结构化文本,输出是结构化数据,规则相对固定,大模型做起来准确率很高。
第二类是组合生成与穷举。拿到结构化后的条件,按照等价类划分、边界值分析、判定表这些经典方法,生成覆盖各种组合的用例。人脑做这个容易累、容易漏,机器做这个正好。
第三类是代码与日志的关联分析。给定一个缺陷现象,去代码库里找相关逻辑,去日志里找异常堆栈,把"现象"和"原因"之间的链路补全。这类动作以前靠人翻代码,现在可以让技能去检索和比对。
2.2 我实际搭建的技能清单
经过几轮迭代,我最终稳定下来的技能有这么几个:
| 技能名称 | 输入 | 输出 | 核心逻辑 |
|---|---|---|---|
| 需求解析器 | 需求文档片段 | 功能点列表、条件表 | 按"触发条件-处理逻辑-预期结果"三元组抽取 |
| 接口契约提取 | 接口定义文件 | 字段约束表 | 解析类型、必填、枚举、边界 |
| 用例生成器 | 条件表+约束表 | 用例集 | 等价类+边界值+场景法组合 |
| 代码检索器 | 关键词/现象描述 | 相关代码片段 | 语义检索+调用链追踪 |
| 日志分析器 | 日志片段 | 异常模式、时间线 | 模式匹配+时序对齐 |
| 缺陷定位器 | 现象+代码+日志 | 可疑位置排序 | 多源证据加权 |
这张表不是一开始就长这样的。最早我只做了"用例生成器",后来发现生成的用例质量不稳定,追根溯源是需求解析那一步抽得不准,才回头补了"需求解析器"。再后来发现缺陷定位还是靠人,又把代码检索和日志分析加进来。技能清单是长出来的,不是设计出来的,这一点后面还会细说。
2.3 为什么不做成一个"全能大技能"
有人可能会问:为什么不直接写一个超级提示词,让模型一次性完成"读需求→生成用例→分析代码→定位缺陷"?我试过,结论是:链路越长,误差累积越严重。第一步抽错一个条件,后面全歪。而且出了问题没法定位是哪一步的锅。
拆成独立技能之后,每个技能可以单独测试、单独优化、单独替换。需求解析器抽得不准,我就专门给它加 few-shot 示例;用例生成器覆盖不全,我就调整它的组合策略。这种"分而治之"的思路,是整套流程能稳定跑起来的前提。
3. 用例生成技能:从需求文本到可执行用例的完整链路
用例生成是整套流程里最先跑通、也是收益最直观的一环。我拿一个中等复杂度的模块做过对比:人工写用例大概需要四到六小时,用技能生成加人工复核,压缩到四十分钟左右,而且覆盖的场景数量是人工的两倍多。下面把这套链路拆开讲。
3.1 需求解析:把"人话"变成"条件表"
需求文档里最常见的一句话是:"用户提交订单时,如果库存充足则扣减库存并生成订单,否则提示库存不足。"人读这句话,脑子里自动就补出了条件分支。但要让机器处理,得先把这句话拆成结构化的东西。
我给需求解析器定的输出格式是这样的:
{ "feature": "订单提交", "conditions": [ {"id": "C1", "desc": "库存充足", "type": "business"}, {"id": "C2", "desc": "库存不足", "type": "business"} ], "actions": [ {"condition_ref": "C1", "action": "扣减库存并生成订单"}, {"condition_ref": "C2", "action": "提示库存不足"} ], "inputs": ["用户ID", "商品ID", "购买数量"], "expected": ["订单状态", "库存变化", "提示信息"] }这个格式的关键在于把"条件"和"动作"分开,并且给每个条件一个 ID。这样后面生成用例的时候,可以明确知道每条用例覆盖了哪个条件,覆盖率是可计算的,而不是拍脑袋说"应该覆盖全了"。
实操中有一个坑:需求文档里经常有隐含条件。比如"库存充足"这句话,其实隐含了"商品存在""商品未下架""购买数量大于零"这些前置条件。解析器如果只抽显式条件,就会漏。我的做法是在提示词里明确要求"列出所有使该条件成立的前置假设",并且在输出里用assumptions字段单独标记。这些假设后面会变成额外的测试用例。
3.2 组合策略:等价类、边界值、场景法怎么配合
拿到条件表之后,用例生成器要做的是把条件组合成具体的测试用例。这里我用的是三层策略:
第一层是等价类划分。把每个输入字段的取值范围分成有效类和无效类。比如"购买数量"这个字段,有效类是 1 到库存数,无效类包括 0、负数、超过库存、非数字。每个等价类至少取一个代表值。
第二层是边界值分析。在等价类的边界上取值,比如库存刚好等于购买数量、购买数量等于库存加一、购买数量等于零。边界值是缺陷高发区,这一层不能省。
第三层是场景法。把多个条件组合成一个完整的业务场景,比如"库存充足+用户有优惠券+收货地址有效"这样的组合。场景法用来覆盖条件之间的交互,等价类和边界值覆盖的是单个字段,场景法覆盖的是字段之间的关系。
这三层的输出会合并去重,最终形成用例集。我实测下来,一个包含五个输入字段、三个业务条件的模块,生成出来的用例大概在六十到八十条之间,人工复核后保留五十条左右,比人工写的三十条覆盖更全。
3.3 用例可执行化:让生成的用例能直接跑
生成的用例如果只是文字描述,价值有限。我做的进一步处理是让用例生成器同时输出可执行的请求模板。比如针对一个 HTTP 接口,用例里会带上完整的请求方法、路径、请求体、预期状态码和预期响应字段。
# 生成的用例示例(伪代码结构) case = { "id": "TC-ORDER-012", "desc": "库存刚好等于购买数量时提交订单", "request": { "method": "POST", "path": "/api/order/submit", "body": {"userId": "U001", "skuId": "S100", "qty": 5} }, "precondition": "该 SKU 库存为 5", "expected": { "status": 200, "body.orderStatus": "CREATED", "body.stockAfter": 0 } }这样生成的用例,可以直接喂给接口测试框架去跑,省掉了"把文字用例翻译成脚本"这一步。这一步的收益在回归测试阶段特别明显——每次版本更新,重新生成一遍用例,直接跑,不用再手工维护脚本。
注意:生成的请求模板一定要经过人工确认再接入自动化。我遇到过生成器把"预期状态码"写成 200 但实际业务返回 201 的情况,如果直接跑,会误报一堆失败。
4. 缺陷定位技能:把"猜原因"变成"查证据"
用例生成解决的是"测什么"的问题,缺陷定位解决的是"为什么错"的问题。后者其实更耗时间——一个线上问题从发现到定位到根因,经常要花掉大半天。我搭缺陷定位技能的初衷,就是把这半天压缩到半小时以内。
4.1 缺陷定位的难点在哪
传统定位流程是这样的:看到报错现象,先猜可能是哪个模块,然后去翻代码,翻着翻着发现调用链很深,又去翻日志,日志里一堆噪音,得一条条筛。整个过程高度依赖对系统的熟悉程度,换个不熟的模块,效率直接腰斩。
更麻烦的是,很多缺陷的现象和原因之间隔了好几层。比如"订单列表加载慢",可能是数据库慢查询,可能是缓存失效,可能是某个下游接口超时,也可能是前端渲染问题。光看现象,根本判断不了往哪个方向查。
4.2 多源证据加权:代码、日志、变更记录一起看
我的做法是让缺陷定位器同时检索三类信息,然后做加权排序:
- 代码证据:根据现象里的关键词(比如接口路径、字段名、错误码),在代码库里做语义检索,找到相关函数和调用链。
- 日志证据:在日志里找同一时间窗口内的异常、警告、慢查询记录,按时间线对齐。
- 变更证据:查最近的代码提交记录,看相关文件在问题出现前后有没有改动。
这三类证据各自给出一个"可疑位置"列表,然后按权重合并。代码证据权重最高,因为它是逻辑的源头;日志证据次之,因为它反映运行时状态;变更证据作为辅助,用来解释"为什么以前没问题现在有问题"。
实际跑下来,这个加权策略能把可疑位置从几十个缩小到三到五个,人工只需要在这几个里面确认就行。
4.3 一次完整的定位过程还原
举个我实际处理的例子。现象是"某接口在特定用户下返回 500"。我把现象描述丢给定位器,它做了这么几件事:
第一步,从现象里抽出关键词"接口路径""500""特定用户",去代码库检索,找到该接口的处理函数,以及它调用的三个下游服务。
第二步,去日志里找同一时间该接口的调用记录,发现异常堆栈指向一个空指针,位置在下游服务 B 的返回处理逻辑里。
第三步,查变更记录,发现下游服务 B 的返回结构在两天前改过,新增了一个可选字段,但接口处理函数没有做空值判断。
三步下来,根因就清楚了:下游改了返回结构,上游没同步适配。整个过程大概花了十几分钟,其中大部分时间是我在确认定位器的结论,而不是自己在翻代码。
这里有个经验:定位器的输出一定要带"证据链",也就是它为什么认为某个位置可疑。没有证据链的结论,即使对了也不敢信,因为没法验证。
5. 技能编排:怎么把独立技能串成一条流水线
单个技能跑通之后,下一个问题是怎么把它们串起来。我的原则是:能并行就并行,能缓存就缓存,能人工介入就留出口。
5.1 编排框架的选择
我试过两种编排方式。一种是用通用的工作流引擎,把每个技能包装成一个节点,用配置文件定义依赖关系。另一种是直接用脚本串,每个技能是一个函数,按顺序调用。
通用引擎的好处是可视化、易维护,坏处是学习成本高,而且调试的时候隔了一层,出问题不好查。脚本的好处是直观、好改,坏处是复杂流程写起来乱。
最后我选的是混合方案:主干流程用脚本串,保证可控;需要并行或者有复杂依赖的部分,用轻量级的任务队列处理。这样既保留了灵活性,又不至于失控。
5.2 数据在技能之间怎么流转
技能之间的数据格式统一用 JSON,每个技能的输出必须符合预定义的 schema。这样做的好处是,任何一个技能出问题,我可以单独拿它的输入和输出来复现,不用跑整条链路。
举个数据流转的例子:
需求文档 → [需求解析器] → 条件表.json → [用例生成器] → 用例集.json → [用例执行器] → 执行结果.json → [缺陷定位器] → 定位报告.json每一环的产物都落盘保存,这样出问题的时候可以逐环排查。我踩过的一个坑是:早期为了省事,技能之间直接传内存对象,结果某个技能偶发失败,根本没法复现,因为输入数据已经没了。落盘之后,这个问题就解决了。
5.3 人工介入点怎么设计
全自动听起来很美,但实际落地必须留人工介入点。我留了三个:
第一个在需求解析之后。解析器抽出的条件表,需要人工扫一眼,确认没有漏掉关键条件。这一步大概花五分钟,但能避免后面全盘皆错。
第二个在用例生成之后。生成的用例集需要人工复核,删掉明显不合理的,补充业务特有的场景。这一步花十五分钟左右。
第三个在缺陷定位报告之后。定位器给出的可疑位置,需要人工确认根因,然后才进入修复流程。
这三个介入点看起来增加了工作量,但实际上它们把"返工"的风险控制在了早期。如果不留介入点,等到用例跑完发现方向错了,返工成本要高得多。
6. 落地过程中踩过的坑与应对
这套东西从想法到稳定运行,大概花了三个月。中间踩的坑不少,挑几个有代表性的说说。
6.1 生成用例的"幻觉覆盖"
早期版本的用例生成器有个毛病:它会生成一些看起来合理、但实际上业务上不可能发生的用例。比如"用户 ID 为空但购买数量为负数且库存充足"这种组合,逻辑上成立,但业务上根本不会出现,因为用户 ID 为空的话请求根本进不来。
这种"幻觉覆盖"的危害在于,它让用例数量虚高,覆盖率看起来很好看,但实际有效的没几条。我的应对办法是在生成器里加一层业务约束过滤,把不可能的组合提前排除。约束规则来自需求文档里的前置条件,以及和开发确认过的系统边界。
6.2 代码检索的"语义漂移"
代码检索器用的是语义匹配,好处是能理解"订单提交"和"创建订单"是一回事,坏处是有时候会漂移。比如搜"库存扣减",它可能把"库存查询"的代码也捞出来,因为语义相近。
这个问题没有完美的解法,我的做法是加一层调用链过滤:检索出来的代码片段,只保留那些在目标接口调用链上的。调用链信息可以从代码的静态分析里拿到,虽然不完美,但能过滤掉大部分无关结果。
6.3 日志分析的"时间对齐"难题
日志分析最头疼的是时间对齐。不同服务的日志时间戳精度不一样,有的到秒,有的到毫秒,还有的有时区问题。早期我直接按时间戳排序,结果经常把不相关的事件排到一起。
后来改成按请求 ID 对齐:每个请求在入口处生成一个唯一 ID,透传到所有下游服务,日志里都带上这个 ID。分析的时候先按请求 ID 分组,再在组内按时间排序。这样对齐准确多了,代价是需要推动各个服务在日志里加请求 ID,算是一个前期投入。
6.4 技能更新的"回归验证"
技能不是写完就完了,后面还要不断调优。每次调整提示词或者换模型,都可能影响输出质量。我吃过一次亏:优化了需求解析器的提示词,结果用例生成器的输入格式对不上了,整条链路跑挂。
从那以后,我给每个技能都建了一套回归用例:准备一批固定的输入,每次改动后跑一遍,对比输出有没有异常变化。这套回归用例不追求覆盖所有情况,但能挡住大部分低级错误。
7. 这套流程适合什么样的团队
最后聊聊适用性。这套东西不是万能的,它有明确的适用边界。
如果你的团队是业务稳定、迭代慢、测试用例几年不变的那种,那这套流程的收益有限,因为用例生成的边际价值不高。但如果你是快速迭代、需求频繁变更、测试人力紧张的团队,那这套流程能明显缓解压力。
另外一个前提是代码和日志的可访问性。缺陷定位技能依赖代码检索和日志分析,如果代码库权限管得很死,或者日志格式混乱没有统一规范,那定位技能的准确率会大打折扣。这种情况下,建议先把日志规范做起来,再考虑上定位技能。
我个人在实际操作中的体会是:不要追求一步到位。先把用例生成跑通,拿到收益,建立信心,再逐步加缺陷定位。每一步都要有可量化的对比,比如"用例编写时间从 X 降到 Y""缺陷定位时间从 A 降到 B"。有了数据,推动后续优化才有说服力。
还有一点:技能的输出永远需要人工把关。Agent Skills 是放大器,不是替代品。它把你从重复劳动里解放出来,让你有精力去做更有价值的判断。把这一点想清楚,落地的时候心态会稳很多。