Agent测试Skill构建实战:SKILL.md+scripts+references三模块深度拆解
2026/9/9 7:05:34 网站建设 项目流程

先交代一下背景。最近团队在折腾Agent落地,我负责给测试组搭一个可复用的测试Skill。前后试了几版方案,最后稳定在标题里写的那套结构上:SKILL.md + scripts + references。这三个月踩了不少坑,也把测试场景从接口冒烟测到回归执行全跑了一遍。这篇文章就是把整个开发过程和思考整理出来,适合正在做Agent技能封装、或者想让模型更稳定地替自己跑测试的人。

先说结论:Skill这事儿,本质不是让模型“记住”你的业务,而是把人的工作套路拆成三份——给模型看的操作手册、给模型调的工具脚本、给模型查的参考资料。测试这个领域尤其适合这么拆,因为测试本身就是高重复、强规范、知识分散的工作。

1. 为什么要专门做测试Skill:先把问题定义清楚

1.1 Agent替人做测试,缺的不是工具而是“套路”

我最早让Agent直接干测试时,效果特别不稳定。比如让它在本地写个脚本去调接口,前几次还能跑通,换成复杂的业务链路就开始胡说八道:要么参数乱猜,要么断言写得像摆设,要么报告输出格式一会儿JSON一会儿Markdown。后来我发现问题不在模型能力,而在于我什么都没给它定。

人做测试是有流程的:先看接口文档,明确入参和边界,再写用例,执行,断言,最后汇总报告。这串动作里既有“知识”(比如常见的边界值法、等价类划分),也有“执行路径”(比如怎么发请求、怎么处理token),还有“经验沉淀”(比如哪些字段最容易出问题)。如果这些全丢给模型临场发挥,它每次都会重新发明一遍轮子,而且经常发明歪。

测试Skill要解决的就是这个问题:把常规测试任务里那些不变的东西固化下来,模型拿到Skill只需要按步骤走,稳定性和效率都会明显上来。

1.2 SKILL.md + scripts + references 这套结构到底在拆解什么

这套结构本质上是把测试能力拆成三个层次:

  • SKILL.md:模型的“大脑皮层”——告诉模型这个技能管什么场景、分几步做、每一步做到什么程度算完。它是模型的工作指令,不是给人看的文档。
  • scripts/:模型的“手”——把可复用的执行逻辑写成脚本,模型需要跑任务时直接调脚本,而不是自己现场敲代码。这样能保证执行结果稳定,也能复用已有的测试框架。
  • references/:模型的“记忆库”——放测试模板、接口文档样例、规范等参考内容。模型只有在需要时才会读取,避免把所有东西都塞进上下文里造成过载。

打个比方,这就像你给新来的测试同事三样东西:一份SOP操作流程、一套现成的自动化测试脚本、一抽屉的测试模板和参考文档。他照SOP走,用工具干活,遇到特殊情况翻抽屉。这套结构的价值就是让Agent按照同样的套路工作。

1.3 哪些人适合用这套结构

  • 团队在用Claude Code、Codex这类支持技能扩展的Agent,想让它稳定输出测试结果
  • 自己做自动化测试,想把手头脚本组织成Agent可调用的工具链
  • 测试负责人想把团队的测试规范和经验沉淀下来,让新人和Agent都能复用

2. 三个核心模块怎么设计:细节决定成败

2.1 SKILL.md:给模型写的“操作手册”

很多人在这一步就翻车了。最常见的问题是:把SKILL.md写成了项目说明文档,全是“本技能用于测试”、“支持哪些功能”,唯独没写模型接到任务后到底该做什么。模型读完一脸茫然,最后还是自由发挥。

我在实际测试项目里摸索出来,一份能用的SKILL.md至少要包含四块内容:

Frontmatter 元信息

最前面用YAML格式写清技能的基本信息。name是技能名,description最关键,它决定了模型什么时候会想到调用这个Skill。description里必须写清触发条件,不是“一个测试技能”这种废话,而是“当用户要求对某个HTTP接口进行自动化测试、验证接口返回是否符合预期、生成接口测试报告时,使用本技能”。要让模型在决策时能精准匹配。

适用场景与不适用场景

明确写清楚这个Skill能干什么、不能干什么,能帮模型省下大量试错时间。比如我的接口测试Skill会写明:“适用:RESTful API的功能测试、参数校验、返回字段断言。不适用:性能压测、UI自动化。”模型读到“不适用”字样,就会主动拒绝跑偏的任务。

工作流程Step

这是SKILL.md的灵魂。要把测试过程拆成固定步骤,比如:

  1. 读取接口文档(优先从references获取)
  2. 梳理接口参数,设计边界值用例
  3. 调用scripts下的脚本执行请求并采集结果
  4. 对返回结果做断言,汇总失败项
  5. 生成测试报告,标记遗留风险

每一步都要给出清晰的输入和产出,让模型像照着菜谱做菜一样。

输出约定与注意事项

测试和写代码不同,Agent经常把输出格式写飘。所以SKILL.md里要明确输出格式:报告用Markdown表格还是JSON,失败用例怎么标注,日志放哪里。我还会加一条“严禁事项”:比如“严禁直接吞异常,要把堆栈信息完整保留到report目录下”。这些细节决定了Agent产出的东西能不能直接用于后续流程。

下面是一个精简版的SKILL.md骨架,给新手参考:

--- name: api-test-skill description: 当用户要求对HTTP接口做自动化测试、验证接口返回是否符合预期、生成测试报告时使用。 --- ## 适用场景 - RESTful API 功能测试 - 参数边界校验 - 返回字段断言 ## 不适用场景 - 性能压测 - UI 自动化 ## 工作流程 1. 读取 references/api-doc-sample.md 了解接口文档结构 2. 根据接口入参设计测试用例,覆盖正常值、边界值、异常值 3. 运行 scripts/run_api_test.py 执行测试 4. 解析输出结果,整理失败项 5. 按 references/report-template.md 生成测试报告 ## 输出约定 - 报告使用 Markdown 表格 - 每个失败用例需包含:用例编号、预期结果、实际结果、失败原因 - 完整日志存放在 logs/ 目录下

写完以后有个检验标准:你把SKILL.md里的说明复制给一个没接触过这个项目的测试工程师,看他能不能照着完成整个测试并生成报告。能,说明模型大概率也能。

2.2 scripts:给模型调用的“工具箱”

scripts目录里的脚本和普通脚本最大的区别在于它们的使用者是模型,不是人。所以脚本设计的核心不是“人看着方便”,而是“机器和模型都好理解”。

我总结出三条原则:

第一,脚本粒度要细。不要写一个“run_all_tests.py”包办所有事情,要拆成单步操作。比如:解析接口文档用parse_api.py,执行测试用run_api_test.py,生成报告用build_report.py。粒度细的好处是模型可以根据实际需求自由组合,而不是被迫跑完一整条链路。

第二,输入输出必须结构化。脚本的参数要从命令行接收,结果要输出成JSON格式。模型最擅长解析JSON,你让它从一大段终端日志里找失败原因,它也能做,但容易误判。直接给它结构化的JSON,准确率会高一个档次。

第三,容错要做得特别厚。脚本使用者是模型,它很有可能会传进来一些奇怪的参数。比如把数值类型传成了字符串,或者把中文标点当成分隔符。所以脚本里要对参数做严格的类型校验,并且错误信息要输出得足够直白,方便模型理解后自行修正。

一个典型的脚本入口长这样:

#!/usr/bin/env python3 """执行单接口的测试用例,输出JSON结果。""" import argparse import json import requests def parse_args(): parser = argparse.ArgumentParser(description="API test runner") parser.add_argument("--url", required=True, help="接口地址") parser.add_argument("--method", default="GET", help="HTTP方法") parser.add_argument("--params", default="{}", help="JSON格式的请求参数") parser.add_argument("--expected", required=True, help="JSON格式的预期结果,如 {\"code\": 0}") return parser.parse_args() def main(): args = parse_args() try: params = json.loads(args.params) except json.JSONDecodeError as e: # 错误信息要输出得足够清晰,模型才能自助修复 print(json.dumps({"ok": False, "error": f"params不是合法JSON: {e}"})) return # ... 执行请求、断言 result = {"ok": True, "response": resp.text, "case_id": "demo"} print(json.dumps(result, ensure_ascii=False)) if __name__ == "__main__": main()

2.3 references:给模型查阅的“记忆库”

references目录是很多人忽略的地方,但它恰恰决定了模型输出的质量上限。模型的知识是通用化的,而测试场景往往是项目特有的——你们公司的接口返回格式是{"code":0,"data":{}}还是{"status":"success","result":{}},模型不可能提前知道。references就是用来补充这部分项目知识的。

那么references里到底该放什么?放三类就够:

1. 接口文档样例。不用把全部接口都放进去,放一份有代表性的文档作为格式样板,并告诉模型“项目接口文档遵循此格式”。模型看到样例,就知道字段说明、参数表、响应结构该怎么理解。

2. 测试用例模板。这是我强烈建议放的。很多模型生成的测试用例停留在“验证接口能通”这个层面,缺少边界值、异常值、参数组合这类测试思维。放一份好的用例模板,相当于给模型补了测试方法论。

3. 报告输出模板。固定报告格式,让所有输出风格统一。

这里有个关键技巧:references里放的是“引用”而不是“全文”。不要把几十个接口文档的完整内容全塞进去,模型读取时会把它们全部拉进上下文,很快就把窗口塞满了。正确做法是放精简的核心模板,同时把完整文档放在项目其他位置,在SKILL.md里写明“完整文档位于docs/目录下,需要时可读取”。

3. 从零实战:构建一个可用的测试Skill

3.1 初始化目录与环境

我习惯用一套固定的目录结构来组织Skill:

api-test-skill/ ├── SKILL.md ├── scripts/ │ ├── run_api_test.py │ └── build_report.py └── references/ ├── api-doc-sample.md ├── test-case-template.md └── report-template.md

创建完目录后,先搭Python虚拟环境,把requests、pytest这些依赖装好。记得在SKILL.md里写清楚运行环境,否则模型执行的时机一长,可能就把环境信息弄丢。

mkdir -p api-test-skill/{scripts,references} cd api-test-skill python3 -m venv .venv source .venv/bin/activate pip install requests pytest

这里有一个经验:scripts目录下的脚本要写成可以直接用绝对路径运行的。因为Agent执行脚本时,当前工作目录不一定是项目根目录,如果脚本里用了相对路径,很容易报找不到文件。

3.2 编写SKILL.md,确定工作流程

我以“对一个REST API接口做冒烟测试”这个典型需求为例。SKILL.md里把工作流定义为四步:

  1. 读取接口文档,提取请求URL、方法、必填参数、关键返回字段
  2. 用references里的测试用例模板,设计一组基础用例(正常请求、缺参数、参数类型异常、空值)
  3. 调用scripts/run_api_test.py逐个执行,收集返回结果
  4. 用scripts/build_report.py生成报告,输出到report目录

工作流定义好后,我习惯在SKILL.md里加入一段“决策提示”,告诉模型什么时候直接跑脚本、什么时候要先看references。比如:

当用户只要求“测一下登录接口通不通”时,直接执行脚本,不需要完整走用例设计流程。当用户要求“详细验证登录接口”时,先读test-case-template.md,再走完整流程。

这能显著提升执行效率,也能防止模型每次一上来就写一堆用例,把简单任务搞得臃肿。

3.3 scripts目录下的核心脚本实现

核心脚本我一般会分两个:执行脚本和报告脚本,职责分离不容易出错。

run_api_test.py:负责接收接口信息,执行请求,输出JSON结果。关键点是对请求参数做类型转换,因为模型从文本里提取参数时经常传成字符串。脚本里我会用类型判断做一次清洗:

def parse_params(raw_params): if isinstance(raw_params, str): raw_params = json.loads(raw_params) return raw_params or {}

build_report.py:读取测试结果,生成Markdown报告。它要同时支持从文件和标准输入读取结果,因为模型可能已经把结果拿在手里,不想先写文件再读文件。

python scripts/build_report.py --input results.json --output report.md

脚本输出格式很重要。我在run_api_test.py里固定输出以下JSON结构,方便模型解析:

{ "case_id": "LOGIN-001", "status": "pass" | "fail", "expected": {"code": 0, "msg": "success"}, "actual": {"code": 1001, "msg": "参数错误"}, "error": "字段code匹配失败,预期0,实际1001", "duration_ms": 125 }

模型拿到这个结构,几乎不用思考就能汇总出测试结论。

3.4 references里的模板设计

references目录下我放了三个文件。其中test-case-template.md是最关键的,它教模型怎么设计用例。模板里我明确规定了用例的格式:

## 用例模板 | 用例编号 | 测试项 | 输入参数 | 预期结果 | 优先级 | |---------|--------|---------|---------|--------| | XXX-001 | 正常请求 | 完整入参 | code=0, 返回业务数据 | P0 | | XXX-002 | 缺少必填参数 | 不传name | code=1001, 提示参数缺失 | P1 | | XXX-003 | 参数类型异常 | name=123 | code=1002, 提示类型错误 | P1 | | XXX-004 | 参数超长 | name=256个字符 | code=1003, 提示超长 | P2 |

模型看到这个模板,就明白“用例不光是验证通的场景,还要考虑异常和边界”,而不是每次都只会发一个GET请求然后说“接口正常”。

api-doc-sample.md则放一份接口文档的样例。我会特别注明:“项目接口返回统一为{"code": int, "msg": string, "data": object},code为0时表示成功。” 模型有了这个信息,在设计断言时就知道该断言什么字段。

3.5 接入Agent流程与调试

目录、脚本、文档都准备好了,剩下就是把它放到Agent能识别的位置。不同Agent的Skill加载机制不太一样,有的是放到特定目录,有的是在配置里声明。我以常见的做法为例:

  • 把整个api-test-skill目录放到Agent的skills目录下
  • 确认Agent能读取到SKILL.md的frontmatter
  • 用一个简单任务测试触发:“请用api-test-skill测试登录接口 http://localhost:8080/login”

第一次跑的时候大概率会有问题。我最常见到的问题有两种:一是模型忽略了SKILL.md里的步骤,自己另起炉灶;二是脚本报错后模型不知道怎么处理。这两种问题的排查思路我在下一节详细说。

调试阶段有个小技巧:在SKILL.md里加一段“debug模式”,要求模型在执行每步时打印当前行为。跑完一轮之后,把调试信息从SKILL.md里去掉,再正式用。这样能快速定位是流程问题还是脚本问题。

4. 实战中的常见问题与排查技巧

4.1 Agent不按SKILL.md走怎么办

这是最让人抓狂的问题:SKILL.md写得清清楚楚,模型偏不照做,非要自己发挥。我排查过很多次,90%的原因出在SKILL.md的description和正文的可执行性上。

场景一:description写得过于宽泛,模型无法判断该不该调用这个Skill。解决办法是把description写成“触发式”,明确包含“当用户要求...时使用”,并且把触发条件写具体。比如“当用户要求对接口进行自动化测试并输出报告时使用”,比“接口测试工具”触发率高得多。

场景二:SKILL.md的工作流太抽象。模型看完不知道第一步该打开哪个文件。解决办法是要把命令写全,比如“运行python scripts/run_api_test.py --url http://localhost:8080/login --method POST执行用例”。模型是命令友好型的生物,你给它具体的命令,它就跑得很稳;你给它抽象描述,它就自由发挥。

如果前面都改好了,模型还是不理,那就检查一下这个Agent版本是不是完整支持SKILL.md的加载。有些框架只识别特定文件名,比如必须叫SKILL.md全大写,改成skill.md就识别不了。这个在接入前一定要确认。

4.2 脚本输出不够结构化,模型解析失败

早期我把脚本结果用print格式化输出,模型解析时经常出错,尤其当返回内容里有大段嵌套JSON时,模型会自己“脑补”字段。后来我改成强制JSON输出,并且在SKILL.md里明确写了“解析scripts输出的JSON对象,不要自行推测字段”,问题就基本消失了。

如果你的脚本必须输出大量日志,建议把日志写到文件里,标准输出只保留JSON结果。这样模型拿到的永远是干净的结构化数据,不会被无关日志干扰。

# 结果写到日志,stdout只输出JSON import json, logging logging.basicConfig(filename="logs/runner.log", level=logging.INFO) result = {"ok": True, "response": resp.text} print(json.dumps(result, ensure_ascii=False))

4.3 references内容太多导致上下文爆炸

很多人喜欢把公司所有的接口文档、测试规范全部塞进references,结果模型加载Skill时上下文被占满,后面的对话越来越笨。我在实战中吃过这个亏。

后来定的规矩是:references只放精简模板和样例,完整文档放在项目docs目录,在SKILL.md里用一句话指引模型需要时再读取。

比如SKILL.md里写:

references/api-doc-sample.md 仅为格式样例。完整接口文档位于 /docs/api-docs/ 目录下,实际测试时请从该目录读取对应接口文档。

这样模型既知道了文档长什么样,又不会一开始就把所有文档读进上下文。这个设计理念非常重要,Skill做大了以后,references的管理直接决定了Agent的使用体验。

4.4 常见问题速查表

问题现象原因解决办法
Agent不调用Skilldescription没有触发词改成“当用户要求...时使用”的句式
Agent跳过步骤乱来工作流太抽象每一步给出明确命令和产出要求
脚本被模型调用时报参数错误模型传参不规范脚本内做类型清洗和错误提示
解析结果总是错输出格式不固定统一JSON输出,日志写入文件
Agent变笨references内容过载只留模板,完整文档外部引用
依赖频繁缺失环境没有固化用requirements.txt锁定依赖版本

5. 结束前的几个实用建议

最后再分享两个我踩坑踩出来的经验。

第一个经验:Skill开发不要一上来就做大而全。我第一次把测试Skill设计得特别完整,包含了接口测试、UI测试、性能测试、安全测试,结果模型经常混淆,反而什么都不精。后来拆成独立Skill,每个只做一件事,效果立刻好了很多。做Skill可以遵循一条原则:先从一个高频场景切进去,跑通了再复制扩展。

第二个经验:SKILL.md里建议留一个“测试验证”章节,里面写上一个最小可执行用例及期望输出。每改一次Skill,就把这个用例跑一遍看是否仍然符合预期。相当于给Skill做回归测试,防止越改越跑偏。

这套结构看起来简单,但真正用好的人不多。核心不在于你会不会写Markdown和Python脚本,而在于你能不能把自己的测试思维拆解清楚。当我们把测试经验真正拆成操作手册、工具脚本和参考模板三块时,Agent就不再是纸上谈兵的聊天机器人,而是个能稳定交付活儿的测试助手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询