尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Postman自动化测试:前置与后置脚本实战指南

Postman自动化测试:前置与后置脚本实战指南 1. 从“发请求”到“管流程”为什么我们需要前置与后置处理如果你用过Postman大概率是从发送一个简单的GET或POST请求开始的。输入URL点击“Send”看到返回的JSON数据任务完成。这就像学会了开车从A点到B点。但真实的接口测试和开发工作远不止“点对点”的驾驶。你很快会遇到一系列问题这个接口需要登录每次都要手动复制粘贴Token吗接口返回的数据里有一个动态的orderId下一个接口要用难道要手打我想批量跑100个测试用例但每个用例开始前都要清理测试数据结束后都要验证数据库状态这怎么自动化这些问题的答案都指向了Postman里两个强大但常被新手忽略的功能前置脚本Pre-request Script和测试脚本Tests Script我们习惯称之为“前置处理”和“后置处理”。它们不是锦上添花而是将Postman从一个“高级HTTP客户端”升级为“自动化接口工作流引擎”的关键。简单来说前置脚本在请求被发送之前执行。它的核心任务是“准备”。比如自动生成签名、计算时间戳、从环境变量或上一个请求的响应中获取并设置Token、准备随机测试数据。后置脚本在收到响应之后执行。它的核心任务是“验证”和“提取”。比如断言响应状态码是否为200、验证响应体某个字段的值、从复杂的JSON结构中提取一个动态值并保存到变量中供后续请求使用。没有它们你的接口测试是孤立的、手动的、脆弱的。有了它们你构建的是一张能自动流转数据、自我验证、具备复杂逻辑的接口测试网络。接下来我会以一个电商订单流程为例带你从零开始深入这两个脚本的每一个细节分享我踩过的坑和总结的最佳实践。2. 脚本的舞台理解Postman的脚本执行环境与变量体系在写第一行代码之前必须搞清楚你的代码在哪里运行能调用什么数据存在哪里。这是避免后面无数诡异报错的基础。2.1 沙盒与内置库你的JavaScript能做什么Postman的脚本基于一个Node.js风格的JavaScript沙盒环境。这意味着你不能直接使用Node.js的fs、path等核心模块来读写本地文件。这是出于安全考虑。但Postman提供了强大的内置对象和库这是你操作请求、响应、变量的唯一途径。pm这是最重要的全局对象几乎所有操作都通过它进行。pm代表“Postman”。pm.request操作当前请求前置脚本中。你可以动态修改请求的URL、头信息、Body。pm.response操作收到的响应后置脚本中。你可以获取状态码、响应头、响应体。pm.environment和pm.collectionVariables管理环境变量和集合变量。pm.globals管理全局变量。pm.test()用于编写测试断言。pm.expect()一个基于Chai.js的断言库语法非常友好。setTimeout()setInterval()支持异步定时操作。crypto-js、lodash、tv4(JSON Schema验证) 等第三方库已内置无需引入即可使用。注意很多新手会尝试写console.log然后去浏览器的开发者工具里找结果发现什么都没有。Postman的脚本输出在它自己的**“控制台”Console** 里。你需要点击Postman左下角的“Console”按钮打开它所有console.log、脚本错误、网络请求详情都会在这里显示。这是你调试脚本最重要的工具没有之一。2.2 变量作用域像管理代码一样管理你的数据变量是连接不同请求的血液。Postman的变量作用域从大到小为全局Global - 集合Collection - 环境Environment - 本地Local - 数据Data。全局变量所有集合、所有环境都可用。慎用通常只放一些极通用的配置如公司网关域名。修改它会影响所有测试。集合变量定义在某个集合Collection下的变量。这个集合内的所有请求都可以访问。这是我最推荐存放业务流程共享数据的地方比如一个业务流程登录-创建订单-支付中的所有接口都属于同一个集合那么流程中产生的token、userId、orderId就应该存为集合变量。环境变量与环境绑定。比如你有“开发环境”、“测试环境”、“生产环境”每个环境的base_url、app_key、app_secret都不同这些就应该定义为环境变量。通过切换环境一套脚本就能跑在不同的平台上。本地变量仅在单次请求的脚本生命周期内有效请求执行完就销毁。常用于脚本内部的临时计算。数据变量来自外部数据文件如CSV、JSON在运行集合Collection Runner或监视器Monitor时传入。变量的存取语法// 设置变量 pm.environment.set(variable_key, variable_value); pm.collectionVariables.set(order_id, 123456); // 获取变量 (推荐这种方式有清晰的回退链本地-环境-集合-全局) let token pm.variables.get(token); // 或者作用域明确的获取 let baseUrl pm.environment.get(base_url); // 使用变量在请求的URL、Headers、Body中 // 格式{{variable_key}} // 例如URL: {{base_url}}/api/login一个关键技巧在“Tests”脚本里pm.variables.get()会按照“本地 - 环境 - 集合 - 全局”的顺序查找变量。而在请求构建时URL、Headers编辑框里直接使用{{var}}语法其查找顺序是不确定的Postman可能会用先找到的那个。因此对于关键变量最好在脚本中显式地用pm.environment.get()或pm.collectionVariables.get()来获取避免歧义。3. 前置脚本实战让请求“聪明”地自我准备前置脚本的核心逻辑是在请求发出前修改pm.request对象。我们通过几个典型场景来掌握它。3.1 场景一动态生成请求签名很多API为了安全需要对请求参数进行签名。签名算法通常涉及对参数排序、拼接密钥、进行MD5或SHA256哈希。手动计算是不可能的必须自动化。假设有一个接口签名规则是将所有请求参数包括Body中的JSON键值对按字母序排序拼接成key1value1key2value2的格式末尾加上app_secret你的密钥然后对整个字符串取MD5放入请求头的X-Sign字段。前置脚本实现// 1. 获取环境变量中的 app_secret const appSecret pm.environment.get(app_secret); // 2. 获取当前请求的所有参数来自URL Params和Body let requestData {}; // 处理URL查询参数 if (pm.request.url.query.all().length 0) { pm.request.url.query.each((item) { requestData[item.key] item.value; }); } // 处理JSON格式的请求体如果是form-data或x-www-form-urlencoded需要另外处理 try { const bodyJson JSON.parse(pm.request.body.raw); Object.assign(requestData, bodyJson); } catch (e) { // 请求体不是JSON可能是其他格式这里简化处理 console.log(Request body is not JSON, skipped for signature.); } // 3. 参数排序并拼接 const sortedKeys Object.keys(requestData).sort(); let signString ; sortedKeys.forEach((key, index) { signString ${key}${requestData[key]}; if (index sortedKeys.length - 1) { signString ; } }); signString app_secret${appSecret}; // 4. 计算MD5 (使用Postman内置的crypto-js) const CryptoJS require(crypto-js); const sign CryptoJS.MD5(signString).toString(); // 5. 将签名添加到请求头 pm.request.headers.add({ key: X-Sign, value: sign }); console.log(Generated Sign String: ${signString}); console.log(Generated X-Sign: ${sign});实操心得算法一致性务必和后台开发确认签名算法的每一个细节包括参数编码URL编码、空值处理、布尔值转换等。一个空格或大小写的差异都会导致签名失败。调试在控制台打印出拼接前的参数对象和最终的签名字符串这是排查签名错误最快的方法。密钥安全app_secret这类敏感信息永远不要写在脚本里必须放在环境变量中。在团队协作时可以将包含敏感信息的初始环境变量文件通过Postman的“分享”功能加密链接或团队工作空间管理而不是直接发代码。3.2 场景二自动处理Token与鉴权这是最常见的前置脚本用途。在需要登录的接口前自动获取并设置Token。方案A在集合级别设置一个获取Token的请求。创建一个“获取Token”的请求如登录接口。在该请求的“Tests”脚本里提取response中的access_token并保存为集合变量pm.collectionVariables.set(“access_token”, jsonData.access_token);在该集合下的其他所有请求的“Pre-request Script”中添加如下脚本// 从集合变量中获取token并添加到Authorization头 const token pm.collectionVariables.get(“access_token”); if (token) { pm.request.headers.add({ key: ‘Authorization’, value: Bearer ${token} }); } else { console.warn(‘Access token is not set in collection variables.’); }运行整个集合时Postman会先执行“获取Token”请求拿到Token存起来后续请求的前置脚本会自动带上它。方案B更健壮的处理——Token过期自动刷新。 有些Token有过期时间如JWT。我们可以在前置脚本中判断Token是否即将过期如果是则先同步调用刷新接口。// 假设我们将token和过期时间都保存在环境变量中 const token pm.environment.get(“access_token”); const expiresAt pm.environment.get(“token_expires_at”); // 存储的是时间戳 const now Math.floor(Date.now() / 1000); // 当前时间戳秒 const bufferTime 300; // 提前5分钟刷新 if (!token || !expiresAt || now bufferTime expiresAt) { // Token无效或即将过期执行同步刷新 console.log(‘Token expired or about to expire, refreshing...’); // 注意在Pre-request Script中不能直接发送异步请求并等待。 // 我们需要使用pm.sendRequest进行同步调用。 const refreshToken pm.environment.get(“refresh_token”); const refreshRequest { url: pm.environment.get(“base_url”) ‘/auth/refresh’, method: ‘POST’, header: { ‘Content-Type’: ‘application/json’ }, body: { mode: ‘raw’, raw: JSON.stringify({ refresh_token: refreshToken }) } }; // 同步发送请求 pm.sendRequest(refreshRequest, function (err, response) { if (err) { console.error(‘Failed to refresh token:’, err); return; } if (response.code 200) { const newToken response.json().access_token; const newExpiresAt now response.json().expires_in; // 假设返回expires_in pm.environment.set(“access_token”, newToken); pm.environment.set(“token_expires_at”, newExpiresAt); console.log(‘Token refreshed successfully.’); // 设置当前请求的Authorization头 pm.request.headers.add({ key: ‘Authorization’, value: Bearer ${newToken} }); } else { console.error(‘Refresh token failed with status:’, response.code); // 可以在这里触发重新登录流程或者让测试失败 } }); } else { // Token有效直接设置 pm.request.headers.add({ key: ‘Authorization’, value: Bearer ${token} }); }踩坑警告pm.sendRequest在Pre-request Script中是异步的虽然我们用了回调函数但Postman不会等待回调函数执行完毕再发送主请求。这可能导致主请求在Token刷新完成前就发出去了。上述代码在简单场景下可能工作但对于强依赖顺序的流程并不完全可靠。更稳健的做法是将Token刷新作为一个独立的请求放在集合的最开始并通过集合运行器Collection Runner确保其顺序执行。前置脚本更适合处理确定性的、无需等待外部响应的逻辑。3.3 场景三生成随机测试数据为了保证测试的独立性和可重复性我们经常需要生成随机的用户名、邮箱、手机号等。// 生成随机字符串 function getRandomString(length) { const chars ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789’; let result ‘’; for (let i 0; i length; i) { result chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } // 生成随机邮箱 const randomEmail test_${getRandomString(8)}example.com; // 生成随机手机号以1开头的11位 const randomMobile ‘1’ Math.floor(1000000000 Math.random() * 9000000000); // 将数据设置到环境或集合变量或在当前请求的Body中使用 pm.environment.set(“random_email”, randomEmail); pm.environment.set(“random_mobile”, randomMobile); // 如果请求体是JSON我们可以直接修改它 try { const requestBody JSON.parse(pm.request.body.raw); requestBody.email randomEmail; requestBody.mobile randomMobile; // 重要修改完对象后需要将更新后的JSON字符串重新赋值给request.body.raw pm.request.body.raw JSON.stringify(requestBody); } catch (e) { console.log(‘Cannot parse request body as JSON, skipping dynamic data injection.’); }这个技巧在压力测试、并发测试中尤其有用可以避免数据冲突。4. 后置脚本实战从“收到响应”到“完成验证与接力”后置脚本Tests的能力更侧重于“验收”和“提取”。它的执行顺序在收到响应之后在查看响应结果之前。4.1 核心任务一编写自动化测试断言断言是自动化测试的基石。Postman内置了基于Chai.js BDD语法的pm.expect非常直观。// 1. 验证状态码 pm.test(“Status code is 200”, function () { pm.response.to.have.status(200); }); // 2. 验证响应时间性能要求 pm.test(“Response time is less than 500ms”, function () { pm.expect(pm.response.responseTime).to.be.below(500); }); // 3. 验证响应头 pm.test(“Content-Type is application/json”, function () { pm.response.to.have.header(“Content-Type”, “application/json”); }); // 4. 验证JSON响应体最常用 const responseJson pm.response.json(); pm.test(“Response has success status”, function () { pm.expect(responseJson.code).to.eql(0); // 假设业务code 0代表成功 pm.expect(responseJson.message).to.eql(“success”); }); pm.test(“User data structure is correct”, function () { pm.expect(responseJson.data).to.be.an(‘object’); pm.expect(responseJson.data).to.have.property(‘userId’); pm.expect(responseJson.data).to.have.property(‘username’); pm.expect(responseJson.data.username).to.be.a(‘string’).and.to.not.be.empty; }); // 5. 使用JSON Schema验证复杂数据结构确保接口契约稳定 const schema { “type”: “object”, “properties”: { “code”: { “type”: “integer” }, “message”: { “type”: “string” }, “data”: { “type”: “object”, “properties”: { “list”: { “type”: “array” }, “total”: { “type”: “integer” } }, “required”: [“list”, “total”] } }, “required”: [“code”, “message”, “data”] }; pm.test(“Schema is valid”, function () { pm.response.to.have.jsonSchema(schema); });断言的最佳实践断言要具体不要只断言状态码200。业务失败也可能返回200但code是错误码。必须同时断言业务状态。一个pm.test只做一件事这样测试报告会更清晰哪个断言失败一目了然。善用pm.expect的链式语法如.to.be.an(‘array’).that.is.not.empty可读性很强。对于关键业务接口使用JSON Schema它能最严格地保证接口返回结构的正确性一旦字段类型或结构发生变化测试会立刻失败防止BUG漏到下游。4.2 核心任务二从响应中提取动态数据这是实现接口间数据传递的核心。提取的数据通常存入变量。// 假设创建订单接口返回{“code”:0, “data”:{“orderId”: “ORD202310270001”}} const responseJson pm.response.json(); if (responseJson.code 0 responseJson.data.orderId) { // 将orderId保存为集合变量因为后续支付、查询订单等请求都需要它 pm.collectionVariables.set(“current_order_id”, responseJson.data.orderId); console.log(Order ID saved: ${responseJson.data.orderId}); // 你也可以同时保存到环境变量但通常集合变量更合适 // pm.environment.set(“current_order_id”, responseJson.data.orderId); } else { console.log(“Failed to extract orderId from response.”); } // 提取更复杂的数据例如数组中的第一个元素的ID // 响应{“data”: {“items”:[{“id”:101, “name”:”A”}, {“id”:102, “name”:”B”}]}} if (pm.response.json().data.items pm.response.json().data.items.length 0) { const firstItemId pm.response.json().data.items[0].id; pm.collectionVariables.set(“first_item_id”, firstItemId); }提取数据的注意事项防御性编程在提取前一定要判断路径上的每个属性是否存在如responseJson.data、responseJson.data.orderId否则如果接口返回结构变化脚本会报错导致整个测试中断。可以使用pm.expect先做验证或者用可选链操作符?.如果Postman的JS引擎支持。变量命名要有意义不要用var1,var2。用order_id,auth_token,user_email这种一看就知道用途的名字。清理变量如果一个变量只在特定流程中使用在流程开始的请求前置脚本中可以先用pm.collectionVariables.unset(“var_name”)清空它避免旧数据干扰。4.3 核心任务三处理复杂的响应验证有时断言不仅仅是检查值是否相等还涉及业务逻辑。// 场景验证返回的订单列表是按创建时间倒序排列的 const orderList pm.response.json().data.list; pm.test(“Order list is sorted by createTime in descending order”, function () { for (let i 0; i orderList.length - 1; i) { const currentTime new Date(orderList[i].createTime).getTime(); const nextTime new Date(orderList[i 1].createTime).getTime(); pm.expect(currentTime).to.be.at.least(nextTime); // 当前时间应大于等于下一个时间 } }); // 场景验证分页参数是否正确 const requestedPage pm.request.url.query.get(“page”) || 1; const requestedSize pm.request.url.query.get(“size”) || 10; pm.test(“Pagination meta is correct”, function () { pm.expect(pm.response.json().data.page).to.eql(Number(requestedPage)); pm.expect(pm.response.json().data.size).to.eql(Number(requestedSize)); pm.expect(pm.response.json().data.list).to.have.lengthOf.at.most(requestedSize); });5. 高阶应用与工程化实践当你熟练掌握了单个请求的前后置脚本就可以把它们组合起来构建强大的自动化工作流。5.1 在集合级别使用脚本你可以在整个Collection的“Pre-request Scripts”和“Tests”标签页添加脚本。这里的脚本会对集合下的每一个请求都生效。集合前置脚本适合放一些全局的初始化操作比如清空某个测试用的全局变量、打印集合开始执行的日志。// Collection Pre-request Script console.log( Start running collection: ${pm.collection.name} ); // 初始化一个计数器 pm.collectionVariables.set(“request_counter”, 0);集合后置脚本适合放一些公共的断言或清理工作比如确保每个请求的响应时间都小于某个阈值、在每个请求后递增计数器。// Collection Tests Script // 公共断言每个请求都应返回JSON pm.test(“Response is JSON”, function () { pm.response.to.be.json; }); // 递增计数器 let counter parseInt(pm.collectionVariables.get(“request_counter”)) 1; pm.collectionVariables.set(“request_counter”, counter);执行顺序对于一个具体的请求脚本执行顺序是集合前置脚本 (Collection Pre-request Script)文件夹前置脚本 (Folder Pre-request Script如果有)请求自身前置脚本 (Request Pre-request Script)发送请求收到响应请求自身后置脚本 (Request Tests Script)文件夹后置脚本 (Folder Tests Script如果有)集合后置脚本 (Collection Tests Script)5.2 使用外部数据文件进行数据驱动测试这是Postman非常强大的功能。你可以将一个CSV或JSON文件作为数据源在运行集合时Postman会遍历文件中的每一行或每个JSON对象并将数据注入到变量中然后执行整个集合。步骤准备一个CSV文件test_data.csvusername,password,expected_status user1,pass123,200 user2,wrongpass,401 ,,400在请求的URL、Body或脚本中使用{{username}},{{password}}来引用这些变量。在请求的Tests脚本中可以使用数据变量进行断言// 从数据文件中读取的预期状态码 const expectedStatus pm.iterationData.get(“expected_status”); pm.test(Status code should be ${expectedStatus}, function () { pm.response.to.have.status(parseInt(expectedStatus)); });打开Collection Runner选择该集合在“Data”栏选择你的CSV文件然后运行。Postman会运行多次迭代每次使用一行数据。这完美解决了需要测试多种输入组合如正确/错误用户名密码、边界值的场景无需手动修改脚本。5.3 脚本模块化与复用当脚本越来越复杂时你需要模块化。Postman允许你在集合的“Variables”标签页中定义预请求脚本和测试脚本的片段但更灵活的方式是利用JavaScript函数。技巧在集合前置脚本中定义公共函数// 在 Collection Pre-request Script 中定义 function generateSignature(params, appSecret) { // ... 签名算法 return sign; } function getRandomEmail(prefix) { return ${prefix}_${Math.random().toString(36).substr(2, 8)}test.com; } // 将函数挂载到全局对象上注意在Postman沙盒中直接var声明可能作用域有限 // 一种常见做法是存到变量里虽然不优雅但有效 pm.collectionVariables.set(“func_generateSignature”, generateSignature.toString()); pm.collectionVariables.set(“func_getRandomEmail”, getRandomEmail.toString());在单个请求的脚本中你可以这样“调用”// 不推荐但可行从变量中取出函数字符串并执行 const funcString pm.collectionVariables.get(“func_generateSignature”); const generateSignature eval((${funcString})); // 使用eval需谨慎 const sign generateSignature(myParams, mySecret);更工程化的做法是将常用的工具函数写在一个单独的JavaScript文件中然后在需要使用的请求的“Pre-request Script”或“Tests”中通过Postman的require功能如果函数被定义为模块或者通过复制粘贴代码片段的方式引入。对于团队协作可以建立一个共享的代码片段库。6. 常见问题、调试技巧与性能考量6.1 调试Console是你的最佳伙伴所有console.log()、console.info()、console.error()的输出以及脚本的语法错误、运行时错误都会在Postman控制台View - Show Postman Console 或 CtrlAltC中显示。这是你排查问题的第一现场。典型调试流程在脚本关键位置添加console.log打印变量状态。发送请求。打开控制台查看输出顺序和值是否符合预期。如果脚本有错误控制台会显示详细的错误信息和堆栈跟踪。6.2 常见错误与解决ReferenceError: variable_name is not defined变量未定义。检查变量名拼写确认变量是否在正确的作用域环境/集合/全局中设置。使用pm.variables.get()前最好先判断是否存在。Cannot read property ‘xxx’ of undefined尝试访问undefined或null值的属性。这是提取响应数据时最常见的错误。务必先用pm.expect(jsonData.data).to.not.be.undefined或if (jsonData jsonData.data)进行防御性判断。脚本修改了请求但发送出去的请求没变确保你修改的是pm.request对象。对于URL参数修改pm.request.url.query对于Header修改pm.request.headers对于Body修改pm.request.body.raw记得重新JSON.stringify。修改后在控制台的请求日志中检查最终发出的请求详情。集合运行器Runner中变量不更新集合运行器有自己的一次性变量作用域。确保你使用pm.collectionVariables.set()而不是pm.variables.set()后者可能设置到本地变量迭代结束就没了。另外在Runner界面可以查看和手动设置每次迭代的变量值。6.3 性能与最佳实践脚本复杂度前置/后置脚本是同步执行的除了pm.sendRequest。过于复杂的计算或循环会阻塞请求发送或结果展示影响体验。对于耗时操作考虑是否有必要。变量滥用不要把所有东西都塞进全局变量。根据生命周期和作用范围选择合适的变量作用域。及时清理不再需要的变量pm.environment.unset。错误处理使用try...catch包裹可能出错的代码块如JSON解析避免单个请求脚本失败导致整个测试流程中断。在catch块中可以用pm.test标记一个失败的测试并给出友好提示。try { const jsonData pm.response.json(); // ... 处理jsonData } catch (e) { pm.test(“Response body is valid JSON”, function () { pm.expect.fail(Failed to parse JSON: ${e.message}); }); }代码注释与文档复杂的业务逻辑如特定的签名算法一定要写注释。对于团队共享的集合可以在请求的“Description”或集合的“Documentation”中写明脚本的用途和依赖。掌握了前置与后置处理Postman才真正成为了你手中的自动化利器。它不再是一个简单的接口调试工具而是一个能够模拟完整用户操作流、自动验证业务规则、高效进行回归测试的测试框架。从下一个接口开始试着为它加上几行脚本你会发现测试工作的效率和可靠性都能得到质的提升。
返回列表