☰
Agent Skills实战:打造可复用的接口自动化测试技能包
2026/10/20 1:46:05 网站建设 项目流程

最近接到一个需求:让团队的 Agent 在不动业务代码的前提下,自动跑接口冒烟测试。折腾了两天,最后用 Agent Skills 的方式整理出一个接口测试技能包,效果比我预期好不少。这里把整套做法和踩坑过程完整复述一遍。

Agent Skills 和普通 Prompt 最大的不同,是它把“技能说明 + 配套脚本 + 参考样例”打包成一个独立目录,Agent 只有在遇到匹配任务时才会加载这套能力,不会无端占上下文。接口测试这个场景特别适合做成技能包:流程固定、规则明确、重复性高,但每次要测的接口又千差万别,正好需要这种“可动态组合”的载体。

这篇文章适合两类人:一类是在做 Agent 应用开发,想让 Agent 具备真实工具操作能力;另一类是测试开发,想把手上的接口测试流程沉淀成可复用的“能力集”。下文里的代码和方法都是我实际跑通过的,你可以直接照抄改抄。

1. 为什么要给 Agent 定制接口测试技能包

1.1 我理解的 Agent Skills 到底是什么

Agent Skills 不是某个独立框架的专属概念,更像是一种工程实践:给智能体追加一个可插拔的“工具箱”。这个工具箱由一组结构化的文件组成,核心入口是一个 Markdown 描述文件,里面写清楚这个技能是干什么的、在什么时候调用、需要哪些输入参数。旁边可以放着 Python 脚本、Shell 脚本、提示词模板、参考数据样例等。

以前做 Agent 工具调用,是把十几个函数直接扔进工具列表,Agent 自己根据函数描述挑来用。但函数一多,选错工具的概率就明显上升。技能包的思路变成了“按需加载”:Agent 先判断当前任务属于哪个技能域,再进入对应技能目录读取完整说明和脚本。相当于以前你扔给 Agent 一堆散件,现在你给了它几个装好的工具箱,每个箱子上贴了标签和使用说明书。

我这边是在某开源 Agent 框架上做的集成,整体思路在主流框架上都通用。你只需要按约定建一个技能目录,框架在启动时扫描到目录,Agent 就能识别并使用这个技能。

1.2 接口测试这个场景为什么适合做技能包

接口测试天然适合 Agent 来执行,原因是它足够“结构化”。一个接口测试任务无非是:根据接口文档或业务需求,明确请求方法、路径、请求头、请求体,然后发出请求,最后对响应做校验。

这套流程里有很多“确定性动作”,例如构建 JSON、解析 JSON、判断状态码、提取字段,这些完全可以交给脚本去做。而 Agent 擅长的是“判断测什么、用什么参数、怎么解读结果”。把两者结合起来,就是接口测试技能包的核心价值。

我举个例子。过去要测一条登录链路,测试人员要先打开文档、复制 URL、填 token、选方法、填参数、跑断言、看报告,手动做一遍至少十几分钟。技能包接入后,我只需要对 Agent 说一句:“请按照接口文档跑一遍用户中心的登录、资料查询、资料更新三段链路,重点检查登录后的 token 是否能穿透到后续接口。”Agent 会自动加载技能、解析文档、生成用例、调用脚本、返回测试结果。

更关键的是,接口文档经常变。如果逻辑都写死在一次性脚本里,接口一变又要改脚本;技能包里的脚本是通用执行器,接口的具体用例由 Agent 根据需求临时生成,改起来就快很多。

1.3 技能包和普通 Prompt 的本质区别

把技能包做成目录而不是把规则写进 Prompt,最大的优势在于上下文管理。你如果在系统 Prompt 里详细描述接口测试的每个步骤、每种断言规则、每个工具用法,这段长度至少占据上万 token,不管当前任务是不是接口测试,模型都想一遍。技能包则完全相反:Agent 在平时不读取技能内部说明,只有当前任务被分类为接口测试时才加载对应目录。

这个模式有点像经验丰富的开发者的“知识库”——我不会把每天要用的工具命令都背在脑子里,而是知道遇到问题去哪查。技能包就是让 Agent 知道去哪查、查到之后怎么执行。

另外,技能包是文件形态,天然支持版本管理和团队复用。我把这个技能包放进 Git 仓库,换一拨人接手,功能、依赖、文档全都在目录里,不依赖某一次对话里的上下文。这一点是普通 Prompt 做不到的。

2. 技能包结构设计与关键文件

2.1 一份通用技能包的目录规范

一个标准的接口测试技能包不需要太多文件,核心脉络是“入口描述 + 执行脚本 + 参考样例”。我常用的目录结构大概是这样的:

api_test_skill/ ├── SKILL.md ├── requirements.txt ├── scripts/ │ ├── run_test.py │ ├── parse_openapi.py │ └── json_extract.py └── assets/ ├── sample_request.json ├── sample_response.json └── examples.md

SKILL.md 是 Agent 读取技能时的第一个文件,也可以说是一份“给 Agent 看的说明书”。scripts 目录放具体的执行逻辑,assets 目录放各种参考数据,让 Agent 在不确定请求格式时可以照着样例写。

这里有一个我在初期走弯路得出的经验:不要把脚本逻辑写得过于复杂。技能包里的脚本应该是“确定性工具”,负责 Agent 不擅长的精确运算,而不是把所有测试规则都写死在代码里。例如解析 JSONPath 这种需要精确匹配的活,脚本搞定;至于测哪些接口、用什么参数,留给你和 Agent 在对话里决策。

2.2 SKILL.md 中的核心字段怎么填

SKILL.md 不是写给人看的普通文档,它是 Agent 的“调用指南”。写得好不好,直接决定 Agent 能不能在恰当时机识别出这个技能。我建议至少包含三块:元信息、调用描述、参数定义。

一份典型的 SKILL.md 开头会长这样:

--- name: api_test_runner description: 执行接口测试任务。当用户要求测试API、验证接口返回、检查接口链路、根据接口文档生成并执行测试用例时使用。技能支持HTTP请求发送、动态参数关联、状态码与JSON字段断言、测试报告生成。 --- # API 接口测试技能 ## 使用场景 需要发起 HTTP 请求并验证响应的任务。 ## 基础指令 1. 先向用户确认被测环境地址、鉴权方式、需要覆盖的接口范围。 2. 如需解析接口文档,调用 scripts/parse_openapi.py。 3. 使用 scripts/run_test.py 发送请求并按断言规则检查结果。 4. 最终给出 Markdown 格式测试报告。 ## 参数定义 - base_url: 被测环境根地址,例如 http://service.test.local - headers: 全局请求头,例如 Authorization 字段 - test_cases: 测试用例列表,每个用例包含 name、method、path、params、body、assertions

注意 description 里一定要写清触发条件,而且要范围收敛。我一开始写的是“处理与HTTP相关的任务”,结果 Agent 在生成文案的时候也来调用我这个接口测试技能,乱得不行。后面改成“仅当用户要求测试接口、验证接口返回内容、检查接口链路时使用”,误调用率就降下来了。

参数定义不是给用户看的,是给 Agent 看的结构化约束。你可以用简洁的 Markdown 列表,也可以用 JSON Schema 风格写明每个字段类型。多数框架对 SKILL.md 的元信息解析都比较宽松,但是字段命名尽量保持稳定,不要一会叫 baseUrl,一会叫 base_url,Agent 容易晕。

2.3 配套模块:脚本、模板、需求说明、样例响应

光有 SKILL.md 描述还不够,客户端工具型技能必须配上实际脚本。我这里准备了三个脚本,各管一摊:

  • run_test.py:负责发送请求、处理超时与重试、执行断言、输出报告。
  • parse_openapi.py:负责解析 OpenAPI/Swagger 文档,提取接口列表和参数约束,供 Agent 生成用例。
  • json_extract.py:负责用 JSONPath 从响应中提取字段,实现接口间参数依赖。

assets 目录里的样例数据同样重要。比如 sample_request.json 可以放一个带鉴权的 POST 请求样例,sample_response.json 放一条典型的 JSON 响应。Agent 在构造用例时,如果能参考这些样例,格式准确率会高很多。examples.md 里我还塞了两条完整用例,一条是登录后拿 token,另一条是查询订单列表并断言字段。

我有一次测试发现,Agent 在没有样例时生成请求体经常少一层嵌套,比如 data 字段忘了包一层。放上样例之后,这个问题基本消失。所以不要小看资产文件的作用。

3. 核心实现:接口测试能力如何拆成可复用模块

3.1 请求执行器:GET、POST、鉴权与超时处理

请求执行器是整个技能包的地基。无论 Agent 要测什么接口,最终都要落到一个稳定的 HTTP 请求发送模块上。

我用 Python 的 requests 库封装了一个带重试的请求函数。核心逻辑如下:

import requests import json import time import os def send_request(method, url, headers=None, json_body=None, timeout=15, max_retries=2): """ 统一请求入口:支持 GET/POST/PUT/DELETE,默认带超时与重试 """ if headers is None: headers = {} if json_body is not None: headers.setdefault("Content-Type", "application/json") for attempt in range(max_retries + 1): try: resp = requests.request( method=method, url=url, headers=headers, json=json_body, timeout=timeout ) # 保留原始响应文本,给后续断言用 return { "status": resp.status_code, "latency_ms": int(resp.elapsed.total_seconds() * 1000), "headers": dict(resp.headers), "body_text": resp.text, "body_json": try_parse_json(resp.text) } except requests.exceptions.Timeout: if attempt >= max_retries: return {"status": -1, "latency_ms": -1, "error": "timeout"} time.sleep(0.5 * (attempt + 1)) except requests.exceptions.ConnectionError: if attempt >= max_retries: return {"status": -1, "latency_ms": -1, "error": "connection_error"} time.sleep(0.5 * (attempt + 1)) def try_parse_json(text): try: return json.loads(text) except Exception: return None

这里有两个小细节值得注意。第一是超时时间不要写死成默认值,接口测试场景里每个接口的响应速度可能差很多,最好让 Agent 传进来,比如 15 秒给足,而重试次数默认 2 次,只对网络层错误做重试,HTTP 4xx、5xx 一般不做盲目重试,因为那可能是业务逻辑错误。第二是 latency_ms 一定要统计,因为很多测试断言要检查“响应时间小于 500ms”,如果没有这个字段,Agent 没法做耗时断言。

鉴权这块我单独做了一个小处理。Authorization 这类通用 header 优先从环境变量或外层配置读取,技能脚本里不硬写死 token。否则换环境时脚本就废了。

3.2 动态数据关联:用 JSONPath 串联接口依赖

接口测试里最常见的痛点不是“一个接口怎么测”,而是“接口之间有依赖”。比如先登录拿 token,再用这个 token 查用户信息,最后把返回的用户 id 塞进更新接口。Agent 能理解这种逻辑,但它提取字段不够稳定,所以我把动态关联做成脚本模块。

用 Python 的 jsonpath-ng 库来实现字段提取,封装成一个小函数:

from jsonpath_ng import parse def extract_by_jsonpath(obj, expr): """ 从对象中按 JSONPath 提取信息。 返回所有命中的值列表;如果没命中返回空列表。 """ if obj is None: return [] try: parser = parse(expr) matches = parser.find(obj) return [match.value for match in matches] except Exception: return [] # 示例:从登录接口响应中取 token resp = send_request("POST", "http://service/login", json_body={"account": "demo", "pwd": "123456"}) tokens = extract_by_jsonpath(resp["body_json"], "$.data.token") if tokens: print("token:", tokens[0])

在实际技能包里,我会把“提取变量”和“渲染变量”放到一起。Agent 生成测试用例时,可以在用例中声明前置接口,例如“先调用 login,把 $.data.token 存为 {token},后续请求头中使用 {token}”。run_test.py 会先执行前置接口,提取字段,然后再渲染主请求。

我之前试过让 Agent 直接用正则去 extract,效果很差,因为接口返回嵌套太深。换用 JSONPath 之后,准确率基本是百分之百。这个环节一定要脚本化,别指望 AI 每次临时推理都能算对复杂路径。

3.3 断言与报告:校验状态码、数据字段、响应耗时

执行完请求之后,下一步就是断言。断言规则也应该从用例参数里传入,而不是写死在代码里。比如一个用例可以这样声明:

test_case = { "name": "正常登录", "method": "POST", "path": "/api/login", "json": {"account": "demo", "pwd": "123456"}, "assert": { "status": 200, "jsonpath_expressions": { "$.code": 0, "$.data.token": {"exists": True}, "$.data.user_type": {"in": [1, 2, 3]} }, "max_latency_ms": 500 } }

断言执行器遍历这些规则,逐条比对。如果没命中,就把失败详情记录下来。最后生成一份 Markdown 报告给 Agent,方便 Agent 直接对用户做总结。

这里有一个认知要转换:Agent 不适合做“精确断言”,但它很适合“失败分析”。因为响应内容可能是动态的,或者业务返回了一个不在预期中的错误码,脚本只能判断通过或失败,而 Agent 可以看失败详情,结合上下文推断是不是接口参数传错了、是不是环境数据没初始化、是不是服务器在发布窗口。所以技能包的报告生成必须要带上响应原文和关键上下文,不要只给一行“通过/失败”。

3.4 让 Agent 自动编排完整测试流程

有了请求执行器、变量关联和断言模块,最后一步是把它们串成工作流。这里我说的不是代码工作流,而是 Agent 的决策逻辑。

我在 SKILL.md 里给 Agent 定义了这样的执行顺序:

  1. 确认测试范围:跟用户确认被测系统、环境地址、是否需要解析接口文档。
  2. 解析接口文档:如果用户给了 OpenAPI 文档或接口说明,调用 parse_openapi.py 把接口列表拉出来。
  3. 生成测试用例:由 Agent 根据业务场景组合用例,尽量覆盖正常链路、异常参数、缺失字段、权限不足四种类型。
  4. 提交执行:调用 run_test.py,按依赖顺序执行。
  5. 分析并汇报:Agent 读取报告,把失败用例和可能的根因整理成给用户看的结论。

这套流程跑稳之后,我基本上只需要给 Agent 一个需求描述和接口文档地址,剩下的活它都能自己干完。当然,对于复杂链路,我会要求 Agent 每个用例执行前打印出“准备调用哪个接口、传什么参数”,方便我随时打断纠正。

4. 手把手实操:从零构建并接入你的 Agent

4.1 初始化技能包目录与环境

第一步很直观,先在本地建目录。我习惯把技能包放在 Agent 配置目录下的 skills 文件夹内,例如:

/my_agent/ ├── skills/ │ └── api_test_skill/

然后在 api_test_skill 里建子目录和虚拟环境:

cd my_agent/skills/api_test_skill mkdir -p scripts assets python -m venv venv source venv/bin/activate pip install requests jsonpath-ng PyYAML

如果你用的是现成 Agent 框架,记得看框架支持的 skills 目录路径,有些框架需要你在配置里显式声明skills_path。这一步不难,但容易漏。

4.2 编写最小可用的 SKILL.md

不用一上来就写得很复杂,先让技能能跑通一个请求。最小的 SKILL.md 我建议这样写:

--- name: api_test_runner description: 仅当用户要求测试接口、验证接口返回结果、检查API链路时使用。该技能可以发送HTTP请求、解析JSON响应、执行断言,并输出测试报告。 --- # 接口测试技能 ## 输入 - base_url - headers(可选) - test_cases ## 步骤 1. 将用户需求拆解为多个 test_case。 2. 调用 scripts/run_test.py --config test_cases.json。 3. 分析报告并回复。 ## 注意事项 - 不要把本技能用于非接口测试任务。 - 动态关联字段使用 JSONPath 语法。

这里我不放完整参数 Schema 了,因为后面的脚本可以直接读取 JSON 配置文件,Agent 只要把用户需求翻译成 test_cases.json 就行。这个模式初期最好用。

4.3 实现请求执行与断言脚本

脚本能跑通才能给 Agent 用。我贴一个简化版 run_test.py,它接收一个 config.json 文件,执行用例并输出 report.md:

#!/usr/bin/env python3 import json import sys from urllib.parse import urljoin from jsonpath_ng import parse def try_parse_json(text): try: return json.loads(text) except Exception: return None def extract(obj, expr): try: return [m.value for m in parse(expr).find(obj)] except Exception: return [] def send_request(method, url, headers=None, json_body=None, timeout=15): import requests try: resp = requests.request(method=method, url=url, headers=headers, json=json_body, timeout=timeout) return { "status": resp.status_code, "latency_ms": int(resp.elapsed.total_seconds() * 1000), "text": resp.text, "json": try_parse_json(resp.text) } except Exception as exc: return {"status": -1, "latency_ms": -1, "error": str(exc), "text": "", "json": None} def run_one_case(base_url, case): url = urljoin(base_url, case["path"]) method = case.get("method", "GET").upper() headers = case.get("headers", {}) json_body = case.get("json", None) result = send_request(method, url, headers=headers, json_body=json_body) assertions = case.get("assert", {}) errors = [] if "status" in assertions and result["status"] != assertions["status"]: errors.append(f"状态码期望 {assertions['status']},实际 {result['status']}") for expr, expect in assertions.get("jsonpath_expressions", {}).items(): values = extract(result["json"], expr) if not values: errors.append(f"JSONPath {expr} 未命中") elif typeof_expect(expect): # 简化处理:若期望值是字典,则只检查是否存在 if isinstance(expect, dict) and expect.get("exists") is True: continue if values and values[0] != expect: errors.append(f"{expr} 期望 {expect},实际 {values[0]}") if "max_latency_ms" in assertions and result["latency_ms"] > assertions["max_latency_ms"]: errors.append(f"响应耗时 {result['latency_ms']}ms 超过阈值 {assertions['max_latency_ms']}ms") return {"case": case, "result": result, "errors": errors} def main(config_path): with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) base_url = config["base_url"].rstrip("/") results = [] for case in config.get("test_cases", []): results.append(run_one_case(base_url, case)) lines = ["# 测试报告", ""] all_pass = True for item in results: if item["errors"]: all_pass = False ... with open("report.md", "w", encoding="utf-8") as f: f.write("\n".join(lines)) print("report.md generated") if __name__ == "__main__": main(sys.argv[1])

真实场景里脚本要再多几百行,但核心逻辑就是这个:负责执行的工具不要引入过多业务判断,把“规则判断”交给 Agent。

4.4 在 Agent 配置中注册技能并跑通 Demo

技能写完,接下来是接入。我这边是在 Agent 的主配置文件里把 skills 路径加进去,类似:

agent: name: qa_bot skills_path: "./skills"

框架启动时会扫描 api_test_skill 目录,读取 SKILL.md 和脚本。然后我在对话窗口里输入:

“帮我测试用户中心登录接口,环境地址 http://127.0.0.1:8000,账号 demo/pwd123,要求返回 code 为 0 且有 token 字段,响应时间小于 800ms。”

Agent 收到任务后,先判断这是接口测试任务,加载技能包;它生成 test_cases.json;调用 run_test.py;最后把 report.md 转成一句简洁结论。整个过程差不多 30 秒。这个 Demo 跑通后,我信心就上来了,后续才逐步加 OpenAPI 解析和链路测试。

5. 接口测试技能包的调试与排错实录

5.1 常见问题速查表

我在使用这个技能包的两个月里,陆陆续续踩了不少问题。整理成一张速查表,方便你直接对着查:

症状主要原因解决办法
Agent 一直不调用技能SKILL.md 的 description 太宽泛或太窄,模型识别不到触发场景重写 description,明确“仅当测试接口、验证返回、检查API链路时使用”
调用技能但参数乱传参数定义不清晰,缺少枚举值和示例在 SKILL.md 增加字段类型、允许值、参考样例
脚本执行报 JSONDecodeError响应体不是合法 JSON,或请求头没带 Content-Type脚本里统一 try_parse_json,并检查请求头设置
动态关联字段总为空JSONPath 写错,或上游接口没执行成功先用调试模式打印上游响应全文,再核对路径
各地环境跑不通技能包里硬编码了地址或 token全部改从环境变量或 config 传入
网络抖动导致误报失败脚本没有重试机制增加带超时和重试的请求发送函数
报告内容太长,Agent 总结困难日志和响应原文全量输出到报告限制输出行数,保留关键字段与失败详情,省略冗长响应体

5.2 我在接入过程中踩过的三个坑

第一个坑是技能描述写得太“野心勃勃”。我最初写的是“处理与HTTP相关的任务”,结果 Agent 在帮我写邮件模板时也去调用技能,频繁报错。后来我改成“仅当用户明确要求测试接口或验证接口返回内容时使用”,误调用的比例明显下降。这提醒我:技能描述写得越聚焦,Agent 的调度越准。

第二个坑是脚本里的鉴权信息。最初为了图方便,我直接在脚本里写了一个测试环境 token,结果换到另一个环境后所有用例都鉴权失败。后来统一改成从环境变量AUTH_TOKEN读取,SKILL.md 里也提醒 Agent 优先使用环境变量中的鉴权信息。这个问题不大,但一旦遇到,排查成本会很高。

第三个坑是响应体解析不严谨。早期我对响应文本直接json.loads,一旦接口返回的是纯文本或空字符串,整个脚本就抛异常。后来我把解析逻辑改成“能解析就解析,解析不了返回 None”,异常处理兜住之后,脚本稳定性高了一个量级。做工具型脚本,防御式编程一定是必修课。

5.3 技能包生产化:日志、超时、鉴权过期与并发

如果只是自己本地玩,上面那套已经够用。但如果你要放在团队里,我建议再做三件事。

第一是加日志。脚本执行过程中,要把每一步的输入输出摘要打印到日志文件里,例如“调用 POST /api/login,状态码 200,耗时 320ms”。不建议打印完整请求体和响应体,一个是占空间,另一个是可能有敏感信息。日志文件要按天轮转;无否则跑半个月之后磁盘就满了。

第二是处理鉴权过期。测试环境很多接口的 token 有效期只有一小时,长时间执行用例经常中途失效。我做了个简单的 token 刷新模块:如果断言发现鉴权失败,就尝试调用 SKILL.md 里配置的 refresh 接口拿到新 token,再重跑受影响用例。虽然实现不复杂,但对长链路测试帮助很大。

第三是并发策略。我看到有人一上来就给技能包加线程池并发执行用例,结果业务系统扛不住,误报一堆。接口测试里,建议默认串行执行;只有在明确都是只读接口、且不影响数据一致性的前提下,再允许并发。稳定永远排在速度前面。

一点个人体会

这套接口测试技能包做下来,我最深刻的感受是:Agent 的价值不在于替你把 curl 命令敲对,而在于它能像测试人员一样思考要测什么、顺序怎么安排、失败代表什么。技能包则是把那个“能思考的大脑”和一个“动手能力稳定的身体”接起来。

最后分享一个小技巧:在 SKILL.md 的末尾加一个 examples 区块,放一条带完整依赖的请求示例,比如“登录成功后,用返回的 token 查询用户资料”。Agent 参考这条链路后,处理类似场景的成功率会有肉眼可见的提升。这个操作几乎零成本,却能让你的技能包立刻“聪明”起来。

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

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

立即咨询