接口测试在项目里经常是“会执行、难设计、更难归因”。用 Postman 手工点接口并不难,难的是把一组接口、环境变量和断言组合成可重复的测试资产;而 AI Agent 的出现,开始把“人告诉 Postman 怎么测”变成“Agent 读取 Postman 资产,自己排测试计划并解释失败原因”。下面这套实现以 Postman Collection 和 Newman 为主线,结合一个 Python 编写的 Agent 骨架,完成“读取接口清单 -> 生成测试计划 -> 注入断言 -> Newman 执行 -> 汇总失败 -> 生成修复建议”的最小闭环。读完可以直接在现有 Postman 资产上改造,也适合作为接口测试智能化的第一个落地模板。
适合已经用过 Postman 的测试开发、后端开发或测试平台工程师。如果你还停留在“用 Postman 手动点一遍接口、看返回是否 200”的阶段,这套思路会帮你把精力从重复劳动转移到测试设计和问题归因上。
1. 为什么接口测试需要引入 AI Agent 来驱动
1.1 接口测试的重复性工作远超想象
接口测试表面上是在“请求接口”,实际工作中消耗时间的却是另一批事情:准备测试数据、梳理接口之间的依赖、判断用哪些参数组合、决定断言应该校验状态码还是业务字段、接口失败后还要区分是环境问题、数据问题还是代码 bug。
在常见团队里,一次中等规模的接口回归,真正执行请求的时间只占一小部分。大量时间被下面几件事消耗:
- 从接口文档或 Postman Collection 里确认请求地址、请求方法、请求头和请求体。
- 为不同场景准备不同的环境变量,比如 baseUrl、token、orderId。
- 手写 pm.test 断言,判断状态码、响应字段、响应时间。
- 回归失败后,打开 Postman 和日志系统,逐个接口确认失败原因。
这些工作非常适合交给程序自动化,但“自动化”并不等于“智能”。普通的自动化脚本只能按照固定规则执行,无法根据接口返回灵活调整用例,也无法在失败时解释最可能的原因。AI Agent 补上的正是“理解上下文、制定计划、观察结果、给出判断”这一层。
1.2 AI Agent 在接口测试链路中的真实位置
AI Agent 不是一个神奇的黑盒,而是一个能根据目标拆解任务、调用外部工具、观察结果并调整下一步动作的程序。放到接口测试场景里,它至少承担四个职责:
- 解析 Postman Collection,把接口清单转换成模型可以理解的文本或结构化数据。
- 制定测试计划,决定哪些接口需要覆盖成功路径、参数缺失、非法参数、权限不足等场景。
- 生成可执行的测试断言,也就是 Postman 的 pm.test 脚本片段。
- 分析 Newman 的执行结果,给出失败原因和修复建议。
这里要注意:Agent 并不能替代 Newman 去发请求。请求的执行仍然交给 Postman/Newman 这套成熟链路,Agent 做的是“生成规则、解读结果”。这种分工很重要,因为生成规则和解读结果恰恰是 LLM 擅长、传统脚本不擅长的部分;而发请求、维护 cookie、处理重定向、生成 HTML 报告,则是专业工具更可靠的部分。
1.3 这套方案的整体工作链路
整套流程可以概括成一条闭环链路:
- 从 Postman 导出 Collection 和 Environment,保存成 JSON 文件。
- Python 脚本解析 Collection,提取接口名称、方法、URL、请求体等信息。
- 调用 LLM 接口,把接口清单和测试要求发送给 Agent。
- Agent 返回一份结构化测试计划,包含每个用例要校验的断言。
- Python 脚本把这些断言写入一个临时 Collection,交给 Newman 执行。
- Newman 运行结束后生成 JSON 报告。
- Python 脚本汇总报告,再交给 Agent 分析失败原因。
- Agent 输出可执行的修复建议,人工确认后修改接口或测试数据。
这条链路没有引入新的测试框架,也没有替代 Postman 的资产体系,而是把 Postman 已有的 Collection、Environment、Newman 执行能力变成 Agent 可以调用的工具。因此,团队里已经存在的 Postman 资产不会浪费。
2. 准备环境:Postman、Newman 与 Agent 程序的依赖关系
2.1 先确认现有 Postman 资产:Collection、Environment、Example
Postman 里最有价值的三类资产是 Collection、Environment 和 Example。
- Collection:一组接口的集合,可以包含文件夹嵌套、请求定义、前置脚本、测试脚本。
- Environment:一组键值对,用于描述不同环境下的变量,比如
baseUrl、token、orderId。 - Example:某个请求的示例响应,通常用于文档化,也可以作为 Agent 生成断言的参考。
本文的落地方式不要求从零创建接口,而是建议你先把团队里已有接口整理成 Collection。如果没有现成 Collection,可以先新建一个最小集合,包含两三个最常回归的接口,比如用户登录、创建订单、查询订单。后续所有实验都围绕这几个接口展开,成本低,也容易验证效果。
2.2 安装 Newman 和 Python 依赖
Newman 是 Postman 官方提供的命令行工具,可以用 Node.js 的 npm 安装。Newman 可以脱离 Postman 图形界面运行 Collection,非常适合接入脚本和 CI/CD。
先确认 Node.js 环境:
node -v npm -v然后全局安装 Newman:
npm install -g newman newman --version如果公司网络环境限制全局安装,也可以把 Newman 作为项目级依赖:
npm init -y npm install -D newmanAgent 程序使用 Python 编写,建议 Python 3.10 及以上版本。创建虚拟环境并安装依赖:
python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate pip install requests pyyaml python-dotenv这里requests用于调用 LLM 接口,python-dotenv用于读取本地环境变量文件,pyyaml用于后续解析 YAML 格式的配置或 CI 文件。如果只是跑通最小闭环,requests和python-dotenv是必须的,pyyaml可以按需安装。
2.3 环境变量与密钥管理
Agent 调用 LLM 需要一个 API Key。不要把密钥直接写死在代码里,也不要提交到 Git 仓库。推荐的做法是用环境变量管理,或者在项目根目录放一个.env文件并加入.gitignore。
需要准备的环境变量如下:
| 变量名 | 用途 | 示例值 |
|---|---|---|
LLM_API_KEY | 调用大模型服务的密钥 | sk-xxxx |
LLM_BASE_URL | LLM 服务地址,兼容 OpenAI Chat Completions 协议 | https://api.openai.com/v1 |
LLM_MODEL | 模型名称 | gpt-4o-mini |
POSTMAN_API_KEY | 可选,用于通过 Postman API 拉取云端 Collection | PMAT-xxxx |
在本地运行时,可以这样设置:
export LLM_API_KEY=sk-xxx export LLM_BASE_URL=https://api.openai.com/v1 export LLM_MODEL=gpt-4o-mini如果使用.env文件:
from dotenv import load_dotenv load_dotenv()不同团队的模型服务协议可能不同。本文示例以 OpenAI 兼容的/chat/completions接口为例,实际项目换成自建模型网关时,只需要调整LLMClient的请求地址和请求体结构。
2.4 学习环境与生产环境的差异
建议先把这套流程跑在本地学习环境,再考虑生产接入。两者的关注点完全不同。
| 维度 | 学习环境 | 生产环境 |
|---|---|---|
| 接口数据 | 本地 Mock 或测试环境接口 | 可能是生产只读接口或预发接口 |
| 测试数据 | 手工构造,允许随机生成 | 需要稳定、可清理、不会污染业务数据 |
| LLM 调用 | 允许反复试错 | 需要考虑成本、超时、熔断、日志脱敏 |
| 权限 | 本机执行 | 需要 CI 权限、密钥管理、审计 |
| 断言质量 | 人工肉眼确认 | 必须有人工 review 和版本管理 |
生产环境不建议直接把 Agent 生成的断言全量应用到正式回归用例。先让 Agent 生成建议,人工确认后再固化,是更稳妥的过渡方式。
3. 让 Agent 能读懂 Postman Collection
3.1 Collection 导出的 JSON 结构怎么看
Postman 导出 Collection 后得到一个 JSON 文件。以 v2.1 版本为例,最小结构大致如下:
{ "info": { "name": "订单服务接口集", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "创建订单", "request": { "method": "POST", "url": { "raw": "https://api.example.com/orders", "host": ["api.example.com"], "path": ["orders"] }, "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\"userId\": 1, \"amount\": 99.9}" } } }, { "name": "查询订单", "request": { "method": "GET", "url": { "raw": "https://api.example.com/orders/{{orderId}}" } } } ] }这里有几个关键点:
info保存 Collection 名称和 schema 版本。不同版本字段略有差异,建议统一使用 v2.1 导出。item是接口数组。如果接口有分组,item会嵌套出现。request里包含 method、url、header、body 等信息。- URL 里的
{{orderId}}是 Postman 变量,真正执行时需要 Environment 文件提供值。
Agent 要做的第一件事,就是从item中递归提取所有request节点,而不是只取第一层。
3.2 用 Python 解析 Collection 并提取接口元信息
先写一个解析函数,支持文件夹嵌套。重点是递归遍历item:
import json from pathlib import Path def load_collection(collection_path: str) -> dict: return json.loads(Path(collection_path).read_text(encoding="utf-8")) def flatten_items(items, parent_name=""): results = [] for item in items: name = f"{parent_name} / {item.get('name', '')}".strip(" /") if "request" in item: results.append({ "name": name, "request": item["request"], "original_item": item }) if "item" in item: results.extend(flatten_items(item["item"], name)) return results if __name__ == "__main__": collection = load_collection("order_service.postman_collection.json") requests_spec = flatten_items(collection.get("item", [])) for req in requests_spec: method = req["request"].get("method", "GET") url_raw = req["request"].get("url", {}).get("raw", "") print(method, url_raw, "->", req["name"])original_item是后续向临时 Collection 注入断言时要用的原始节点引用。解析阶段先把它保存下来,可以避免重复查找。
运行后,控制台会输出类似结果:
POST https://api.example.com/orders -> 创建订单 GET https://api.example.com/orders/{{orderId}} -> 查询订单如果打印结果为空,说明 Collection 的item字段不是标准路径,需要检查导出的 schema 版本。
3.3 把接口信息转成 Agent 可以理解的上下文
LLM 不直接理解 JSON 数组里的所有字段,也不适合把整个 Collection 原样塞进 Prompt。建议把接口信息整理成结构化文本,让模型快速抓住重点。
def build_context(requests_spec): lines = [] for idx, item in enumerate(requests_spec): req = item["request"] method = req.get("method", "GET") url = req.get("url", {}).get("raw", "") name = item["name"] lines.append(f"{idx}. [{method}] {url} # {name}") body = req.get("body") or {} raw = body.get("raw", "") if raw: lines.append(f" body: {raw[:200]}") headers = req.get("header", []) if headers: header_text = ", ".join( f"{h.get('key')}={h.get('value')}" for h in headers[:5] ) lines.append(f" headers: {header_text}") return "\n".join(lines)这样生成的上下文大概长这样:
0. [POST] https://api.example.com/orders # 创建订单 body: {"userId": 1, "amount": 99.9} headers: Content-Type=application/json 1. [GET] https://api.example.com/orders/{{orderId}} # 查询订单这里的{{orderId}}仍然是占位符。真正的值会由 Newman 执行时从 Environment 文件注入,Agent 只需要知道这里存在一个变量即可。
3.4 这一步容易踩的三个坑
第一,只遍历第一层item,漏掉文件夹里的接口。Postman Collection 经常按模块分文件夹,解析时必须递归。如果只读第一层,Agent 看到的接口清单会不全。
第二,没有处理 Postman 变量。Collection 里大量出现{{baseUrl}}、{{token}}、{{orderId}},如果直接把占位符发给 Agent,Agent 会误以为接口地址不完整。正确做法是执行时通过 Newman 的-e参数传入 Environment 文件,让变量在真实请求阶段替换。
第三,盲目相信 LLM 返回的 JSON。大模型偶尔会返回 Markdown 代码块、解释性文字或多余字段。解析时要做兼容处理,先剥离代码块标记,再尝试json.loads,解析失败时要记录原始返回,方便排查。
4. 编写 Agent 的核心逻辑:从生成用例到执行计划的闭环
4.1 设计一个面向接口测试的 Agent 提示词
Agent 的行为主要受系统提示词控制。面向接口测试的提示词要解决两件事:一是让模型明白自己的角色和输出格式,二是约束模型不要瞎编业务预期。
一个可用的系统提示词如下:
你是一个接口测试专家。你会收到一个 Postman Collection 中待测接口的清单。 请生成一份 Newman 可以执行的测试计划。 要求: 1. 输出 JSON 数组,不要输出多余文字。 2. 每个元素包含 requestIndex、caseName、assertions。 3. requestIndex 对应接口清单中的编号。 4. assertions 是 Postman 的 pm.test 脚本片段。 5. 优先覆盖成功路径、参数缺失、非法参数、状态码和关键业务字段。 6. 没有明确预期时,只校验状态码和响应结构,不要猜测具体业务含义。用户消息则把接口上下文和本次测试目标拼接在一起:
user_prompt = f""" 本次测试目标:对订单服务接口做冒烟回归。 接口清单: {context} 请生成测试计划 JSON。 """这里的关键是“不要猜测具体业务含义”。订单接口是否必须返回orderId、创建失败时返回什么业务码,这些信息 Agent 并不知道。它只能基于接口名和请求体做合理推断。明确这一条,可以避免断言过度。
4.2 实现一个最小 LLMClient 和 JSON 解析器
以 OpenAI 兼容的 Chat Completions 接口为例,写一个最小客户端:
import os import requests class LLMClient: def __init__(self): self.api_key = os.environ["LLM_API_KEY"] self.base_url = os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1") self.model = os.environ.get("LLM_MODEL", "gpt-4o-mini") def chat(self, system: str, user: str) -> str: resp = requests.post( f"{self.base_url}/chat/completions", headers={"Authorization": f"Bearer {self.api_key}"}, json={ "model": self.model, "messages": [ {"role": "system", "content": system}, {"role": "user", "content": user}, ], "temperature": 0.2, }, timeout=60, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]如果团队使用自建的大模型网关,只需要修改base_url和请求体字段,不需要改 Agent 的整体设计。
LLM 返回的内容不一定是干净 JSON,需要做一层兼容解析:
import json def parse_llm_json(text: str): text = text.strip() if text.startswith("```"): text = text.strip("`") if text.startswith("json"): text = text[4:].strip() return json.loads(text)这里兼容了最常见的 Markdown 代码块包裹情况。如果 LLM 输出中包含大量解释文字,建议在 Prompt 中强调“只输出 JSON”,并在解析失败时记录原始文本,而不是直接抛异常后丢弃。
4.3 把测试计划注入 Collection,再交给 Newman 执行
Agent 返回的测试计划大致长这样:
[ { "requestIndex": 0, "caseName": "创建订单-成功路径", "assertions": [ "pm.test(\"状态码为200\", function () { pm.response.to.have.status(200); });", "pm.test(\"订单号存在\", function () { const data = pm.response.json(); pm.expect(data.orderId).to.not.be.empty; });" ] } ]Python 脚本需要把这个测试计划合并到一个临时 Collection 中。合并方式是给对应请求节点添加event字段,写入pm.test脚本:
import copy def add_assertions_to_collection(collection, test_plan): new_collection = copy.deepcopy(collection) items = flatten_items(new_collection.get("item", [])) for case in test_plan: idx = case.get("requestIndex") if not (0 <= idx < len(items)): continue original_item = items[idx]["original_item"] script_lines = [] for assertion in case.get("assertions", []): script_lines.extend(assertion.splitlines()) event = { "listen": "test", "script": { "type": "text/javascript", "exec": script_lines } } original_item.setdefault("event", []).append(event) return new_collection注入完成后,可以把临时 Collection 写入文件:
with open("tmp_collection.json", "w", encoding="utf-8") as f: json.dump(temp_collection, f, ensure_ascii=False, indent=2)然后调用 Newman 执行:
import subprocess def run_newman(collection_file, environment_file=None, output_file="newman_result.json"): cmd = [ "newman", "run", collection_file, "--reporters", "json", "--reporter-json-export", output_file, ] if environment_file: cmd += ["-e", environment_file] result = subprocess.run(cmd, capture_output=True, text=True) return result.returncode, output_file这里要注意:Newman 执行失败时进程退出码非 0,但这时候仍然有 JSON 报告文件。后续分析脚本不要因为returncode != 0就直接终止,否则会丢掉最关键的失败信息。
4.4 把执行结果反馈给 Agent 做失败分析
Newman 的 JSON 报告通常包含run.stats和run.failures。先写一个摘要函数:
def summarize_newman_report(report_path): with open(report_path, encoding="utf-8") as f: report = json.load(f) run = report.get("run", {}) stats = run.get("stats", {}) failures = run.get("failures", []) lines = [ f"totalRequests: {stats.get('requests', {}).get('total', 0)}", f"failedRequests: {stats.get('requests', {}).get('failed', 0)}", f"totalAssertions: {stats.get('assertions', {}).get('total', 0)}", f"failedAssertions: {stats.get('assertions', {}).get('failed', 0)}", ] for failure in failures[:20]: lines.append( f"FAIL at {failure.get('at', '')}: " f"{failure.get('error', {}).get('test', '')}" ) return "\n".join(lines)不同版本的 Newman 报告字段可能略有差异。落地前先用一个已知失败用例验证stats和failures的路径是否正确。
拿到摘要后,再调用一次 LLM,让 Agent 做归因分析:
analysis_system_prompt = """ 你正在分析一次接口测试的 Newman 执行结果。 请根据摘要区分四类失败原因: 1. 接口代码 bug。 2. 测试数据问题。 3. 断言过严或断言写错。 4. 环境变量或依赖服务问题。 请给出最可能的原因和可执行的修复建议。 """ analysis_result = llm.chat(analysis_system_prompt, summary) print(analysis_result)这一轮不重新解析接口,也不重新生成用例,而是专门做“结果解读”。把生成和解读拆成两个独立环节,能让每个 Prompt 更短、更聚焦,也更容易排查问题。
5. 把 Agent、Postman 和 Newman 串成一条可复用流水线
5.1 项目结构建议
最小可复用项目可以按照这个目录组织:
agent-postman-test/ ├── main.py ├── llm_client.py ├── collection_parser.py ├── test_runner.py ├── analyzer.py ├── requirements.txt ├── collections/ │ ├── order_service.postman_collection.json │ └── order_service.postman_environment.json └── logs/各文件职责如下:
main.py:编排主流程,把解析、生成、执行、分析串起来。llm_client.py:封装 LLM 调用。collection_parser.py:解析 Collection、构建上下文、注入断言。test_runner.py:封装 Newman 执行和报告摘要。analyzer.py:封装失败分析 Prompt 和结果输出。requirements.txt:记录 Python 依赖。
项目规模不需要大,关键是把“读资产、生成用例、执行、归因”四个环节解耦,方便单独验证和替换。
5.2 主流程编排脚本
main.py的核心逻辑可以简化成下面的样子:
import json from dotenv import load_dotenv from collection_parser import ( load_collection, flatten_items, build_context, add_assertions_to_collection, ) from llm_client import LLMClient from test_runner import run_newman, summarize_newman_report from analyzer import build_analysis def main(collection_path, environment_path): load_dotenv() collection = load_collection(collection_path) requests_spec = flatten_items(collection.get("item", [])) context = build_context(requests_spec) llm = LLMClient() system_prompt = """ 你是一个接口测试专家。你会收到一个 Postman Collection 中待测接口的清单。 请生成一份 Newman 可以执行的测试计划。 要求输出 JSON 数组,不要输出多余文字。 每个元素包含 requestIndex、caseName、assertions。 assertions 是 Postman 的 pm.test 脚本片段。 """ user_prompt = f"本次测试目标:对核心接口做冒烟回归。\n接口清单:\n{context}\n请生成测试计划 JSON。" raw_plan = llm