1. 为什么要做关联:接口测试的“传递链条”
做接口测试的人,十有八九都会遇到同一个场景:登录接口返回了一个token,后面查询订单、修改资料、提交支付全都要带上这个token。你当然可以手动复制粘贴,一次两次没问题,但当你跑完一条完整的业务链路,几十个接口串在一起,每次都手抄token,既慢又容易抄错。更别提回归测试的时候,token过期了、重新登录了,整个集合又得手动改一遍。
这就是接口关联要解决的问题。所谓Postman关联,本质就是让接口之间能够自动传递数据——前一个接口的响应结果,被提取出来存成变量,后一个接口再通过变量引用的方式自动取值。这套机制用好了,你的测试脚本就真正“活”了起来,不再是孤立的单个请求,而是一条能自我流转的数据链。
我之前带团队做电商项目的接口自动化,最核心的几条主链路——登录拿token、下单拿订单号、支付拿流水号、查询拿状态——全部靠关联串起来。一开始新同学总是手动复制,跑到第三步就经常因为漏改一个参数而失败。后来统一改成用Postman的关联机制,整个集合跑一遍只需一键,稳定性和效率都上来了。这篇文章就系统梳理一下我在实际项目中常用的数据提取方式,以及背后那些文档里不会写明白的细节。
适合谁来读?刚接触Postman接口测试、想搞明白“怎么把一个接口的数自动传给下一个接口”的测试新人,以及已经会用Postman但每次都是手动复制参数、想提升自动化水平的同学。读完你至少能掌握四五种提取方式,并且知道在什么场景下用哪一种最合适。
2. 数据提取方式的完整视图:先分清三大维度再动手
在动手写提取脚本之前,我建议先建立一张地图。数据提取这个事,表面上看是一行代码的事,实际背后牵扯到三个维度:从哪提取、用什么语法提取、提取完存到哪。
这三个维度搞不清楚,你很容易陷入一种很尴尬的状态:在网上找到一段提取代码,粘贴进来能跑通,换个接口又不灵了,然后就开始瞎试。我见过太多这样的同事,花了一下午在各种正则表达式里翻滚,最后发现其实是提取位置选错了。
2.1 按响应位置划分:Body、Headers、Cookie
Postman的响应数据分三个主要区域,对应三种不同的提取目标:
- Body(响应体):绝大多数业务数据都在这里,JSON格式居多。比如登录接口返回的access_token、用户信息接口返回的user_id、订单接口返回的order_no。这是最常打交道的区域。
- Headers(响应头):有些服务端习惯把token、session id放在响应头的自定义字段里,比如
Authorization、X-Token、Set-Cookie。我做过一个老系统的接口,token既不放在Body也不放在Cookie,而是塞在一个叫X-Auth-Token的响应头里,不知道的人找半天都找不到。 - Cookie:基于会话的验证方式在传统项目中仍然很常见。登录成功后服务端下发一个Session ID到Cookie,后续请求自动携带。Postman对Cookie有自动管理机制,但某些场景仍需手动提取。
2.2 按提取语法划分:JSONPath、正则表达式、文本处理
- JSONPath:专门用于从JSON结构里取数据,写法类似
data.token、data.user_info.id。这是最推荐、最直观的方式,前提是响应体是合法JSON。 - 正则表达式:用于从任意文本中提取匹配模式的内容。当响应体是HTML、XML、纯文本,或者你想从一段JSON字符串中“硬抠”某个值时,正则就是兜底方案。
- 文本处理函数:Postman的Tests脚本里还可以用JavaScript的字符串方法,比如
split()、substring()、indexOf()组合处理,虽然笨拙,但在某些脏数据场景下反而最稳。
2.3 按存放位置划分:环境变量、全局变量、集合变量
提取出来的数据得有个“容器”装着,Postman提供了三类变量容器:
| 变量类型 | 作用范围 | 典型使用场景 |
|---|---|---|
| 全局变量(Globals) | 所有集合、所有环境都能访问 | 通用配置,比如基础URL、公共测试账号 |
| 环境变量(Environment) | 仅在指定环境下生效 | 区分开发、测试、生产环境的token、域名 |
| 集合变量(Collection Variables) | 仅当前集合内生效 | 与具体业务链路强相关的数据,比如订单号、用户ID |
我的使用习惯是:运行环境相关配置放环境变量,业务链路的中间数据放集合变量,真正全局通用的东西才放全局变量。太多人图省事一股脑塞Globals,结果多环境并行测试时各种串数据,排查起来非常酸爽。
3. 五种主流数据提取方式实战拆解
这五种方式,基本覆盖了我在实际项目中遇到的95%的场景。我会逐个讲清楚适用场景、标准写法、注意事项,以及我踩过的坑。
3.1 JSON响应提取:最推荐、最稳的方案
这是Postman中最常用、也最友好的提取方式。只要接口返回的是标准JSON,用一行pm.response.json()就能把响应体解析成JavaScript对象,然后像操作普通对象一样取值。
// 获取响应JSON对象 const jsonData = pm.response.json(); // 提取token(假设结构:{ "data": { "token": "abc123" } }) const token = jsonData.data.token; // 存入环境变量,供后续请求使用 pm.environment.set("token", token);这里有几个细节值得强调:
第一,pm.response.json()的底层实现是先把响应体字符串做JSON.parse,如果响应体不是合法JSON,这一步会直接抛异常,导致测试脚本中断。所以稳妥的写法是加个try-catch,或者先判断响应头里的Content-Type是否为application/json。
try { const jsonData = pm.response.json(); pm.environment.set("token", jsonData.data.token); } catch (e) { console.log("响应体不是合法JSON", e); }第二,如果返回的是数组结构,需要通过索引取值。比如获取用户列表第一个用户的ID:
const jsonData = pm.response.json(); // 假设结构:{ "data": [ { "id": 1, "name": "张三" }, ... ] } const userId = jsonData.data[0].id; pm.environment.set("userId", userId);第三,当JSON层级很深时,逐层点属性容易因某个中间层为null而报错。比如jsonData.data.user.profile.id,如果user字段缺了,整行就崩了。可以用可选链写法(Postman的Node版本支持)来防御:
const userId = jsonData?.data?.user?.profile?.id; if (userId) { pm.environment.set("userId", userId); }另外,可以用变量来动态指定JSONPath的键名。比如这个例子中,字段名本身是从上一个接口动态获取的,你就可以先取出键名,再通过[]语法取属性:
const keyName = pm.environment.get("fieldName"); const value = jsonData.data[keyName];3.2 正则表达式提取:兜底万能方案
正则提取是稳定性仅次于JSON提取的方案,适用于任何文本内容。Postman里提供pm.response.text()方法拿原始响应字符串,然后配合JavaScript的match()方法提取目标内容。
场景一:响应体是JSON,但结构不规范(比如某个字段值里混了多余字符),你想硬取。
const responseText = pm.response.text(); // 提取"token":"xxx"中的xxx const matchResult = responseText.match(/"token"\s*:\s*"([^"]+)"/); if (matchResult) { pm.environment.set("token", matchResult[1]); }场景二:响应体是HTML或XML,JSON提取完全没法用。比如一个老系统返回的是一段XML,里面包含一个<sessionId>abc123</sessionId>。
const responseText = pm.response.text(); const matchResult = responseText.match(/<sessionId>(.*?)<\/sessionId>/); if (matchResult) { pm.environment.set("sessionId", matchResult[1]); }正则提取的坑主要体现在两个方面:
一是贪婪匹配。默认情况下.*会尽量匹配更多内容,这在某些场景下会“多吃”字符。比如字符串a1b2c3,正则a.*b贪婪模式下会匹配到a1b而不是a1b,看起来一样,但如果目标字符串是a1b2b,贪婪模式会匹配整个a1b2b。所以提取时建议用非贪婪模式.*?:
// 错误示例:贪婪可能匹配过多 /"id":\s*(\d+).*"name":\s*"([^"]+)"/ // 正确示例:用?限制范围 /"id":\s*(\d+).*?"name":\s*"([^"]+)"/二是转义问题。正则里的特殊字符,如点号、括号、反斜杠,都需要用\转义。很多人写正则提取JSON中的邮箱时,写成/.*(.+?)@(.+?).*/结果匹配不到,就是因为漏了转义。
我自己的习惯是:能不用正则就不用正则。正则的可读性差、调试成本高、还容易因服务端返回格式微调而全部失效。只有碰到JSON提取解决不了的场景才祭出正则这把“大锤”。
3.3 响应头提取:拿Token的另一种姿势
有些系统不像常规RESTful API那样把token放在响应体里,而是放在响应头的自定义字段中。比如我之前对接过一个内部平台,登录接口返回后,token放在名为X-Access-Token的响应头里,Body里只有一堆用户资料,不少新手第一次接这种接口都懵了。
Postman提供pm.response.headers对象来访问响应头:
// 方式一:通过all()遍历 const headers = pm.response.headers.all(); let accessToken = ""; headers.forEach(item => { if (item.key === "X-Access-Token") { accessToken = item.value; } }); // 方式二:直接按key查(更简洁) const accessToken = pm.response.headers.get("X-Access-Token"); pm.environment.set("token", accessToken);注意,headers.get()对大小写不敏感,所以X-Access-Token和x-access-token都能查到。但如果响应头里同一个key出现多次(这种情况在Set-Cookie上比较常见),get()只返回第一个匹配值,需要遍历才能拿到完整的。
另一个容易被忽略的细节是响应头的值可能带前缀,比如Authorization: Bearer eyJhbGciOi...,你需要先拆掉前缀再存:
const authHeader = pm.response.headers.get("Authorization"); // 去掉"Bearer "前缀 const token = authHeader.replace(/^Bearer\s+/i, ""); pm.environment.set("token", token);3.4 Cookie提取与自动管理的双轨制
Postman对Cookie有一套自动管理机制:如果你在请求配置里启用了“Enable Cookie Jar”,登录接口返回的Set-Cookie会被自动存储,后续同域请求会自动携带,不需要手动提取。
但自动管理在以下场景会失效:
- 你要拿Cookie里的某个具体值(比如
JSESSIONID)传给不同域名的请求,或者放进请求体里。 - 接口的Cookie策略比较特殊,Postman的Cookie Jar没有正确捕获。
- 多个测试环境并行时,Cookie相互干扰。
这时候就需要手动提取。Postman里没有专门的pm.response.cookies,但可以从响应头的Set-Cookie字段里解析:
const setCookieHeader = pm.response.headers.get("Set-Cookie"); if (setCookieHeader) { // 格式类似:JSESSIONID=abc123; Path=/; HttpOnly const matchResult = setCookieHeader.match(/JSESSIONID=([^;]+)/); if (matchResult) { pm.environment.set("sessionId", matchResult[1]); } }这里有个坑,Set-Cookie可能不止一个,比如一次响应里同时下发SESSIONID和USER_COOKIE,每个都是独立的响应头值。如果headers.get()只拿到第一个,你可能提取不到想要的那个。稳妥做法是遍历所有头,再拼接匹配:
const headers = pm.response.headers.all(); headers.forEach(item => { if (item.key.toLowerCase() === "set-cookie") { const matchResult = item.value.match(/USER_COOKIE=([^;]+)/); if (matchResult) { pm.environment.set("userCookie", matchResult[1]); } } });3.5 处理嵌套结构与动态字段名
接口返回的数据不会永远是扁平的{ "token": "xxx" },更多时候是嵌套好几层的对象和数组。比如订单详情接口返回:
{ "code": 0, "data": { "order_list": [ { "order_id": "ORD20250101001", "items": [ { "sku_id": "SKU001", "price": 99.00 } ] } ] } }你想提取第一个订单的ID,或者第一个商品的SKU,写法就是:
const jsonData = pm.response.json(); const orderId = jsonData.data.order_list[0].order_id; const skuId = jsonData.data.order_list[0].items[0].sku_id; pm.environment.set("orderId", orderId);这种逐层访问的写法,最大的风险是某个中间层null了。服务端大促期间数据异常,order_list可能为空数组,或者接口直接返回错误对象,取值的时候就会报Cannot read properties of undefined。我写提取脚本时都会加一层空值判断,宁可多写两行,也不要跑挂了之后半夜被报警电话吵醒。
另外有一种比较骚的场景:字段名是动态的。比如按时间戳返回数据的接口,响应结构是:
{ "data": { "20250101120000": { "count": 10 }, "20250101130000": { "count": 20 } } }你需要取出第一个时间戳对应的count值。这时就得用Object.keys()动态获取属性名:
const jsonData = pm.response.json(); const keys = Object.keys(jsonData.data); if (keys.length > 0) { const firstKey = keys[0]; const count = jsonData.data[firstKey].count; pm.environment.set("count", count); }这种写法我在做数据看板类接口时经常用到,普通JSON取值方式完全推不动。
4. 关联落地全流程:从登录Token到一条完整业务链
提取方式掌握得再多,最终也要落到完整流程里才能发挥作用。下面我用最典型的“登录—创建订单—查询订单”三个接口,完整演示一遍关联的搭建过程。这是接口测试里最基础也最核心的链路,你能把这条链路走通,后面遇到复杂的业务场景只是同样的套路换个变量名。
4.1 第一步:规划变量命名与环境
很多新手一上来就乱写变量名,token、Token、TOKEN混着用,跑到后面都不知道自己存的到底是哪个。我的建议是:建立一套统一的命名规范,最好带前缀区分用途。
| 变量名 | 含义 | 示例 |
|---|---|---|
baseUrl | 环境基础地址 | https://api.test.com |
authToken | 登录令牌 | eyJhbGciOi... |
userId | 当前用户ID | 10086 |
orderId | 创建的订单号 | ORD20250101001 |
requestId | 流水号 | REQ123456 |
然后在Postman里先建好环境:点击右上角眼睛图标 → Environments → Create New Environment,填入环境名和基础变量。我这里创建了一个叫Test的环境,先放baseUrl和公共账号信息。
4.2 第二步:登录接口中提取并存储Token
登录接口的请求体是账号密码,发送后响应体通常会返回token。我们在Tests标签页里写提取脚本:
const jsonData = pm.response.json(); // 断言接口返回成功 pm.test("登录返回code为0", function () { pm.expect(jsonData.code).to.eql(0); }); // 提取token并存入环境变量 pm.environment.set("authToken", jsonData.data.token); pm.environment.set("userId", jsonData.data.user_info.id);注意,Tests标签页里的脚本是在接口响应返回后自动执行的,所以可以在这里做断言+取值二合一。但如果取值失败,比如接口返回了错误信息而不是token,jsonData.data.token会报错,而且整个脚本中断。更稳妥的方式是断言和提取分开处理,或者用上一节提到的try-catch包裹。
我个人的习惯是:断言单独写一个pm.test,提取脚本用try-catch包一层,这样即使提取失败,测试结论里也能看到是断言挂了还是提取挂了,排查起来不用从头翻日志。
4.3 第三步:创建订单接口中引用Token
创建订单接口请求头需要带上登录拿到的token,请求体里可能需要用到用户ID。在请求的Headers标签页里,把token字段的值写成:
Authorization = Bearer {{authToken}}在Body(JSON格式)里,把用户ID写为:
{ "user_id": "{{userId}}", "product_id": "SKU001", "quantity": 1 }Postman的{{变量名}}语法会在发送请求时从当前环境变量中自动取值替换。这里有个容易踩的大坑:环境选错了。如果当前没选中Test环境,{{authToken}}就替换不了,请求头发出去是个空值,服务端直接401。所以我每次在Postman右上角都要确认当前环境是哪套,尤其是多环境并行测试的时候,更是要小心。
创建订单接口成功之后,我们还得把响应里的orderId提取出来存好,留给下一个接口用。所以在创建订单接口的Tests里再写:
const jsonData = pm.response.json(); pm.test("创建订单返回成功", function () { pm.expect(jsonData.code).to.eql(0); }); if (jsonData?.data?.order_id) { pm.environment.set("orderId", jsonData.data.order_id); }4.4 第四步:查询订单接口中验证关联结果
查询订单接口,请求参数或路径里引用上一步存的订单号:
GET {{baseUrl}}/order/{{orderId}}同时请求头里依然带上{{authToken}}。发送请求后,如果返回的订单号正是刚才创建的那一个,说明整条链路的关联已经跑通了。
到这里,一条简单的数据链路就闭环了:登录提取token → 创建订单提取orderId → 查询订单引用orderId。你手动把这三个接口按顺序点一遍,每一步的取值都在自动进行。如果再配合Postman Collection Runner或者Newman命令行工具,这条链路就能一键自动化跑回归。
4.5 高阶用法:在Tests里做二次加工
有时提取出来的数据不能直接用,需要加工。最常见的场景就是对token过期时间的处理。有些登录接口返回的token里带了过期时间戳,你存变量时可以同时用当前时间判断有效期,如果快过期了,就先重新登录再取值:
const jsonData = pm.response.json(); const token = jsonData.data.token; const expiresIn = jsonData.data.expires_in; // 秒为单位 // 计算过期时间并写入日志 const expireAt = Date.now() + expiresIn * 1000; console.log("Token将在", new Date(expireAt).toLocaleString(), "过期"); pm.environment.set("authToken", token);这种做法在自动化回归测试里很有价值,因为token过期是导致接口测试批量失败的“第一大杀手”。很多团队一跑半夜的定时任务就收到一堆401报错,就是因为没做token有效期管理。你可以在校验请求前先做一次token有效性的简单判断,思路比死等报错再处理要主动得多。
5. 关联测试中的常见问题与排查技巧
这部分我整理了几个最典型的问题,都是我在实际项目中踩过的坑,做成速查表方便你对照排查。
5.1 变量提取不到,请求里显示原样{{xxx}}
这种是最常见的,现象是发送请求后{{authToken}}没有被替换,服务端收到的是字面量字符串。
原因一:环境变量没有存上。可能提取脚本里赋值失败了,常见原因是JSON路径写错。排查方法是在Tests里加一行console.log(jsonData),打开Postman的Console(快捷按钮在左下角)看响应体结构,再回去改路径。
原因二:当前没选中环境。右上角环境选择器没选对,或者选的是另一个环境。排查方法:点开环境变量的值,看是否为空。这个原因占了至少三成,别笑,真的很多人被它卡住过。
原因三:变量名拼写不一致。提取时存的是authToken,引用时写的{{auth_Token}},少个下划线天然匹配不上。建议一律用小写+下划线风格命名,并养成用自动补全的习惯。
5.2 Token失效导致批量请求401
原因:登录接口和业务接口之间间隔时间太长,或者token刷新机制没处理好。
解决方案有三层:
- 第一层:在业务集合最前面加一个“前置请求脚本”,执行前先重新登录并刷新token,保证token是最新的。
- 第二层:在Tests里检查响应状态码,发现401就调用登录接口重新提取token,再重发本请求(重发逻辑可以借助
pm.sendRequest实现)。 - 第三层:利用Postman的
setNextRequest()控制执行顺序,让登录请求始终优先执行。
// 集合级别前置脚本示例:开始前先保证token有效 const requestOptions = { url: pm.environment.get("baseUrl") + "/login", method: "POST", header: "Content-Type: application/json", body: { mode: "raw", raw: JSON.stringify({ username: pm.environment.get("username"), password: pm.environment.get("password") }) } }; pm.sendRequest(requestOptions, function (err, res) { if (err) { console.log("登录失败", err); return; } const jsonData = res.json(); pm.environment.set("authToken", jsonData.data.token); });5.3 关联数据在循环迭代时串号
用Collection Runner跑多组数据时,如果每组数据都创建了订单,但orderId一直存的是同一个变量,那么下一次迭代时,上一次的orderId会被覆盖。如果某个接口执行失败,保存的orderId可能还是上一轮的,导致后续请求查错了数据。
解决方案是:在迭代开始前清空或重置关键变量,或者用更有辨识度的变量名(比如追加迭代序号)。我习惯在集合的“Pre-request Script”里重置变量:
pm.environment.unset("orderId"); pm.environment.unset("requestId");这样每轮迭代开始时,上一轮的“脏数据”就被清掉了,避免串号。
5.4 响应体是JSON但pm.response.json()报错
出现这个问题的原因通常是响应体不是纯JSON,而是带BOM头、或者夹杂了HTML内容。比如某些网关超时返回的是一段HTML错误页,但HTTP状态码还是200。
排查方法:先用console.log(pm.response.text())看原始内容,确认里面的结构。如果夹杂了非JSON字符,就需要先用正则把JSON部分截出来再解析:
const responseText = pm.response.text(); // 从HTML或包装结构中截取JSON const jsonStart = responseText.indexOf('{'); const jsonEnd = responseText.lastIndexOf('}'); const jsonStr = responseText.substring(jsonStart, jsonEnd + 1); const jsonData = JSON.parse(jsonStr);5.5 不同环境切换时变量值错乱
开发、测试、生产环境共用一套变量名但值不同,切换环境后引用的值还是旧的。根本原因:把本该放环境变量的放到了全局变量。排查思路:检查你存的变量到底在哪个作用域里。右键点击变量名,可以查看在哪个容器里。建议环境相关变量一律放环境变量,全局变量只放公共静态配置。
我个人的小技巧是:在变量名里带环境前缀,比如test_authToken、prod_authToken。虽然看起来冗余,但多环境并行调试时非常清晰,永远不会拿错。
6. 一些让关联更顺手的扩展技巧
正文的核心内容已经覆盖,最后分享几个我日常工作中总结的顺手技巧,帮助你快速判断和排查问题。
6.1 用Postman Console实时盯变量
Postman左下角有个Console图标,点开之后能看到每个请求的详细日志,包括请求头、请求体、响应数据,以及你在脚本里打的console.log。排查关联问题时,第一步永远是打开Console跑一遍流程,看每个接口的提取脚本是否执行成功、变量是否赋值成功。这比盲目改脚本高效得多。
6.2 利用集合变量做业务快照
如果你需要在测试过程中随时查看某条业务数据(比如当前订单的状态),可以把关键业务数据同时写入集合变量,然后在Postman界面右侧快速查看,不需要再手动发查询请求。
pm.collectionVariables.set("currentOrderStatus", jsonData.data.status);这个变量在集合内所有请求中都能引用,适合快速传递状态数据。
6.3 用pm.sendRequest做数据预置
有些业务链路需要前置数据,比如创建订单前必须先有商品库存。你可以在集合的“Pre-request Script”里用pm.sendRequest动态调用接口来准备数据。比如先查库存,如果不足就调用补库存接口,最后再把商品ID存入变量供后续请求使用。这样整个集合跑起来更稳定,不会因为前置数据缺失而中断。
6.4 关联与数据驱动的搭配
如果关联变量需要同时支持多组测试数据,可以配合Postman的Data文件(CSV/JSON)一起使用。数据文件里的字段用{{字段名}}引用,而接口之间关联的数据用环境变量或集合变量引用。这样既能做参数化批量测试,又能维持接口之间的数据传递。
我举个实际例子:订单接口的测试数据文件里有一列product_id,数据驱动跑每一行时,orderId都是从关联提取的新值,两者互不冲突。这是目前做接口自动化最推荐的组合方式,既灵活又规范。
7. 最后的几句个人体会
从我这几年的实际经验来看,把关联做好,是接口测试自动化从“能跑”到“跑得稳”的关键分水岭。很多人一开始只会在单个接口里手动调参数,觉得自动化很玄学,其实核心就两件事:会提取变量、会引用变量。这两件事打通之后,剩下的就是重复工作。
这套方法最奇妙的地方在于,当你把一条业务链路完整串起来之后,后续的日常回归、冒烟测试都变得非常简单。只需要配置好定时任务,整个Postman集合就能自动跑起来,你只要看最后的测试报告就行。我亲眼见过一个测试团队,以前回归十几个接口要一个多小时,改成自动化之后十分钟内跑完,接口问题还能第一时间报出来,效率和信心都在成倍增长。
最后送你一个小技巧:真正遇到提取不到数据的时候,先不要急着改脚本,打开Console看原始响应结构,用肉眼确认字段路径,再动手写提取。这个顺序能帮你省掉至少一半的试错时间。