ARTICLE DETAIL

资讯详情

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

Postman 断言实战:从状态码到响应体的接口测试核心技巧

Postman 断言实战:从状态码到响应体的接口测试核心技巧 你有没有遇到过这种情况接口在 Postman 里一点“Send”返回 200绿油油一片于是你自信地跟开发说“接口没问题”。结果接口一上线前端页面拿不到数据一查日志才发现后端虽然返回 200但响应体里塞的是一段错误信息。问题出在哪就出在“只看状态码不做断言”。Postman 的断言就是这个环节的核心工具。它能在请求返回后自动检查响应数据是否符合预期状态码、响应体、响应头、响应时间都能验一旦结果不对就飘红告诉你“这轮测试挂了”。这篇文章我会把 Postman 里最常用的断言逐一拆开讲同时从执行机制的角度说清楚断言到底是怎么工作的适合刚接触接口测试、想把测试脚本写得规范一点的同学也适合已经在用 Postman 但每次只会复制粘贴代码片段、完全不理解含义的人。1. 断言是什么先搞清楚它在接口测试里的位置很多人以为“跑接口测试 用 Postman 发请求”。其实不对。发请求只是提交数据验证返回结果是否符合业务预期才是接口测试的核心动作。而这个“验证”的动作就是断言。1.1 没写断言的接口测试基本等于“只测了连通性”我见过不少团队的接口用例是这种状态打开一个接口填好参数点 Send看到 200 就截图贴到缺陷单里标记为“通过”。说实话这一步只能证明“服务没挂”证明不了“接口逻辑正确”。举个最简单的例子。你去查一个用户详情接口传入一个不存在的用户 ID后端处理出错时没做兜底直接返回了 500传统意义上的“接口通了”就不成立。但更隐蔽的情况是后端兜底了返回“操作失败”的 JSON但 HTTP 状态码仍然是 200。此时你不看响应体根本发现不了问题。更麻烦的是这类漏测问题要等到前端联调、甚至上线后由真实用户触发成本一下子就上去了。断言就是干这个的。它把你对接口的所有预期——状态码是什么、返回结构长什么样、关键字段的值是什么、响应时间在什么范围内——写成一段可执行脚本。请求发完脚本自动跑逐条比对。全部符合测试通过有一条不符立刻红牌警告。1.2 Postman 里的断言到底写在哪个位置打开 Postman点开一个请求你会看到请求地址栏下方有几个标签页Params、Headers、Body、Pre-request Script、Tests。许多人常年只用前三个对后面两个视而不见。断言写在Tests标签页里。这里就是一段 JavaScript 运行环境Postman 在收到响应后会自动执行这里面所有代码。注意和 Pre-request Script 区分后者是在发送请求之前执行的经常用来做签名、设置动态参数不负责结果校验。Tests 标签页里最基础的断言结构长这样pm.test(状态码是200, function () { pm.response.to.have.status(200); });pm.test接收两个参数第一个是这条断言的名称会直接显示在测试结果列表里第二个是函数函数里写具体的校验逻辑。如果函数内的断言通过名称前显示绿色对勾失败则显示红色叉号。就这么简单。1.3 断言的核心四类场景Postman 内置了大量断言方法但日常接口测试用到的场景基本可以归成四类场景典型断言对象常用方法状态合理性HTTP 状态码pm.response.to.have.status(200)内容正确性响应体/JSON 字段pm.expect(jsonData.code).to.eql(0)链路完整性响应头/ Cookiepm.response.to.have.header(Content-Type)性能底线响应时间pm.expect(pm.response.responseTime).to.be.below(500)这四类基本覆盖了 90% 以上的接口测试场景。第 2 节我逐个展开讲代码写法和容易踩的坑。2. Postman 常用断言逐个拆解代码、效果与误区新手刚开始写断言最需要的是“拿来就用”的代码但光复制代码不够还得知道每句脚本在干什么遇到报错才知道怎么改。2.1 响应状态码断言最基础但最容易被误解状态码断言有两种写法。第一种是 Postman 提供的快捷方法pm.test(状态码是200, function () { pm.response.to.have.status(200); });第二种是通用断言风格pm.test(状态码是200, function () { pm.expect(pm.response.code).to.eql(200); });两种写法效果一样区别在于第一种是 Postman 内置的简化语法专门针对“响应状态码校验”这个高频场景第二种通过pm.expect拿到响应状态码的值再比较更通用适合批量判断状态码属于某一个范围的情况。如果需要判断“2xx 范围”的状态码可以这样写pm.test(状态码在2xx范围, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 202, 204]); });需要提醒的是状态码断言只是底线。我见过很多人只做了“200 断言”就觉得测试完成了结果响应体里返回的是一段 HTML 报错页面这种场景在网关鉴权失效时特别常见。所以状态码断言一定要配合响应体断言一起用。2.2 响应体字符串断言适合粗糙校验当一个接口返回的是纯文本、HTML 或你只关心某个关键字是否存在时可以直接用字符串断言。pm.test(响应体中包含期望的提示信息, function () { pm.expect(pm.response.text()).to.include(操作成功); });这里有几个常用方法to.include(xxx)包含某个字符串to.not.include(xxx)不包含某个字符串to.equal(xxx)整个响应体和字符串完全相等字符串断言的优点是书写简单缺点是容易误判。比如你想验证“用户名重复”结果响应体里无论是“用户名重复”还是“用户名不重复”都包含“用户名”三个字那断言就失效了。所以字符串断言只适合“粗筛”精细校验还得看 JSON 断言。2.3 JSON 响应体断言接口测试的重头戏现在大多数业务接口返回的都是 JSON所以 JSON 断言是使用频率最高、也最容易出问题的部分。核心思路是先把响应体解析成 JS 对象再对对象里的字段做各种校验。const res pm.response.json(); pm.test(业务状态码为0, function () { pm.expect(res.code).to.eql(0); }); pm.test(message字段非空, function () { pm.expect(res.message).to.not.be.empty; });第一行const res pm.response.json()是把 JSON 字符串解析成 JavaScript 对象的关键步骤。注意如果响应体本身不是合法的 JSON这一步会直接抛异常导致后续断言全部失败。这也是新手最常见的问题来源之一。JSON 断言还能做得更细。比如判断数组长度、判断嵌套字段、判断字段类型const res pm.response.json(); pm.test(data数组长度大于0, function () { pm.expect(res.data.length).to.be.greaterThan(0); }); pm.test(userId字段是数字类型, function () { pm.expect(res.data.userId).to.be.a(number); }); pm.test(订单状态字段值为PAID, function () { pm.expect(res.data.orderInfo.status).to.eql(PAID); });这里用得最多的就是to.eql。它做的是“深度相等”比较也就是说如果你比较的是一个数组或者对象它会逐个元素、逐个属性去比对而不只是比对引用地址。这一点比to.equal更适合测 JSON 对象整体结构。2.4 响应头断言验证 Content-Type 与自定义头响应头经常被忽略但遇到编码问题、跨域问题、文件下载场景时响应头就是关键证据。Postman 里可以通过pm.response.headers拿到所有响应头。pm.test(Content-Type包含application/json, function () { pm.expect(pm.response.headers.get(Content-Type)).to.include(application/json); });还可以直接判断某个响应头是否存在pm.test(响应头包含X-Request-Id, function () { pm.response.to.have.header(X-Request-Id); });跨域场景下经常需要判断Access-Control-Allow-Origin是否符合预期原理一样。需要提一句headers.get(Content-Type)获取到的值可能带charsetutf-8之类的额外信息所以用include而不是equal更稳妥。2.5 响应时间断言给接口性能画一条底线接口再慢也不能无限等。Postman 里响应时间单位是毫秒可以直接拿到做比较。pm.test(响应时间小于1000ms, function () { pm.expect(pm.response.responseTime).to.be.below(1000); });除了to.be.below(1000)还有to.be.above(500)可以判断“大于某个值”。响应时间断言适合做基线控制比如核心查询接口 500ms 以内算达标。但要注意本机调试时的响应时间和线上环境差异很大建议先在目标环境多跑几次拿到合理基线再写死阈值。2.6 环境变量与全局变量断言验证数据流转是否正确接口测试中经常需要把上一个接口的返回值传给下一个接口这时就要配合环境变量。断言同样可以校验变量是否已经被正确赋值。pm.test(token已保存到环境变量, function () { const token pm.environment.get(token); pm.expect(token).to.not.be.undefined; pm.expect(token.length).to.be.greaterThan(0); });pm.environment.get是读取环境变量pm.globals.get是读取全局变量pm.variables.get则是读取当前请求级别的变量。三者的优先级和作用域不同环境变量最常用。这个断言经常用在“上一个接口请求成功、但环境变量没有正确保存”的排查场景能帮我们快速定位是哪一环断了。3. 断言的工作原理从点击 Send 到结果打勾Postman 到底做了什么知其然也要知其所以然。明白了 Postman 断言的运行机制遇到脚本报错时就不会两眼一抹黑。3.1 代码执行的先后顺序Pre-request Script 在前Tests 在后Postman 中一次完整请求的执行顺序是先说 Pre-request Script再发送 HTTP 请求收到响应后执行 Tests 脚本最后把结果渲染到界面上。这个顺序很关键。很多初学者在 Pre-request Script 里写了对上一个接口响应结果的断言结果总是拿不到值就是因为搞混了执行时机——Pre-request Script 跑的时候本次请求还没发出去更不可能有响应数据。同理想在 Tests 里给本次请求动态加上签名参数也是不行的因为请求已经发出去了加参数晚了。3.2 神奇的 pm 对象Postman 送给每个脚本的万能工具你在 Tests 里写的每一段脚本都运行在一个由 Postman 创建的特殊环境中。这个环境里预置了一个叫做pm的全局对象它帮你把接口测试时需要用到的各种数据都集中在一个命名空间里。你可以简单理解成pm是 Postman 送给每个脚本的一把万能瑞士军刀。常用的几个属性和方法成员作用pm.response当前请求的响应对象包含状态码、响应头、响应体pm.request当前请求对象可以拿到请求头和请求体pm.expect断言函数和 Chai 断言库的 expect 用法一致pm.test定义一个测试用例pm.environment环境变量操作入口pm.globals全局变量操作入口pm.variables当前请求级变量操作入口pm.collectionVariables集合级变量操作入口pm.response内部还有两个常用子属性pm.response.code是 HTTP 状态码pm.response.responseTime是响应毫秒数pm.response.text()可以拿到原始响应字符串pm.response.json()可以把 JSON 响应体解析成对象。3.3 pm.test 的包装机制如何保证多条断言互不干扰pm.test本身是一个“包装器”。它会把传入的函数放进一个 try-catch 里执行。函数内部如果抛出了异常比如字段不存在、类型不匹配、断言失败Postman 会把这个异常捕获住记为该条测试用例失败但不会影响其他测试用例的运行。这是 Postman 断言设计得很巧妙的一点。假设你在一个 Tests 里写了五条断言第三条访问了不存在的属性导致报错前两条和后两条仍然会正常执行并给出结果。这样你在排查问题时能同时看到“哪些是对的、哪些是错的”而不是因为一条报错导致整个脚本中断。看一下这个例子pm.test(断言code字段, function () { const res pm.response.json(); pm.expect(res.code).to.eql(0); }); pm.test(断言data字段, function () { const res pm.response.json(); pm.expect(res.data.name).to.eql(张三); });假设接口没返回data字段第一条断言依然会通过第二条断言会因为res.data是undefined而报错。两条互不干扰。3.4 pm.expect 背后的断言库Chai 的 expect 风格如果你接触过 Node.js 生态对 Chai 断言库一定不陌生。Postman 内置了 Chai 的 expect 风格语法所有的pm.expect其实就是 Chai 的expect。链条式语法是 Chai 的特点。比如pm.expect(res.code).to.eql(0)读起来就是“期望 res.code 等于 0”。中间那个to在语义上只是为了连贯不参与比较真正的比较动作在最后一个方法上。你可以把它们当作一条自然语言来读。常用链式方法整理如下to.eql(value)深度相等比较值是否完全一致to.equal(value)严格相等相当于 JavaScript 的to.include(value)包含某个值。对字符串表示包含子串对数组表示包含元素对对象表示包含属性to.be.a(string)判断类型也可以写成to.be.a(number)、to.be.a(array)to.have.property(name)判断对象是否有某个属性to.be.empty判断数组、对象或字符串是否为空to.be.true/to.be.false判断布尔值to.be.above(number)/to.be.below(number)判断大小to.be.oneOf([...])判断是否属于数组中的某个值明白了这些方法后你会发现 Postman 断言写来写去就那么几个套路真正要花心思的是“你要验证什么预期”而不是“怎么写代码”。4. 实测演示两个接口场景把断言串起来用单独讲方法容易让人觉得零散我拿两个真实业务场景把断言串起来跑一遍你会发现接口测试的完整链路其实很顺。4.1 场景一用户登录接口的断言设计假设有一个登录接口地址是/api/login传参是用户名和密码正常返回如下 JSON{ code: 0, message: success, data: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, userName: 测试用户, expiresIn: 7200 } }在 Tests 标签页里我会分几条断言去覆盖这个响应const res pm.response.json(); pm.test(登录接口返回HTTP 200, function () { pm.response.to.have.status(200); }); pm.test(业务状态码为0, function () { pm.expect(res.code).to.eql(0); }); pm.test(响应消息为success, function () { pm.expect(res.message).to.eql(success); }); pm.test(token字段存在且非空, function () { pm.expect(res.data).to.have.property(token); pm.expect(res.data.token.length).to.be.greaterThan(0); }); pm.test(响应时间小于1秒, function () { pm.expect(pm.response.responseTime).to.be.below(1000); });这里有个小细节检查 token 时先判断res.data有没有token这个属性再判断 token 长度大于 0。两步分开是为了定位问题更精准——如果哪天接口少返回了 token 字段报错信息会直接说是“缺少属性”而不是“访问 undefined 的长度报错”排查效率完全不同。断言通过后把 token 保存到环境变量里供后续接口使用pm.environment.set(token, res.data.token);这一步通常不放在断言里直接写在 Tests 标签页顶层即可。但要注意它是无条件执行的如果接口失败且没有返回 token这里会抛异常。想稳妥一点可以在保存前判断 token 是否存在。4.2 场景二查询订单接口的断言设计第二个接口是登录后才能访问的订单列表接口/api/orders。请求头里要带Authorization: Bearer {{token}}返回如下{ code: 0, message: success, data: { list: [ { orderId: A1001, status: PAID, amount: 299.00 }, { orderId: A1002, status: UNPAID, amount: 59.00 } ], total: 2 } }测试脚本会这么写const res pm.response.json(); pm.test(订单接口返回业务成功, function () { pm.expect(res.code).to.eql(0); }); pm.test(订单列表非空, function () { pm.expect(res.data.list.length).to.be.greaterThan(0); }); pm.test(订单总数与列表长度一致, function () { pm.expect(res.data.total).to.eql(res.data.list.length); }); pm.test(每个订单都包含orderId字段, function () { res.data.list.forEach(function (order) { pm.expect(order).to.have.property(orderId); }); }); pm.test(已支付订单金额大于0, function () { const paidOrders res.data.list.filter(function (order) { return order.status PAID; }); paidOrders.forEach(function (order) { pm.expect(order.amount).to.be.greaterThan(0); }); });这个场景里有三个思路值得借鉴。一是“断言业务字段间的逻辑关系”比如total和list.length是否一致这种断言往往比单纯验某个字段更能发现深层次问题。二是“用 forEach 去遍历数组里的元素”确保列表中的每一条数据都符合规范而不是只检查第一条。三是“先过滤再断言”只对满足条件的对象做校验这样不会因为无关数据导致误报。4.3 怎么看断言结果Tests 面板的绿点和红点跑完请求后Postman 底部会有一个 Test Results 面板。每一条pm.test的命名都会出现在这里通过时前面是绿色对勾失败时是红色叉号并同时显示断言库抛出的错误信息。在 Collection Runner 批量执行时每个接口的断言结果会汇总到最终报告中。如果一个集合里有 50 个请求、200 条断言最终报告中会显示“200 条里通过了 196 条、失败了 4 条”并且会标出是哪几个请求的哪几个断言出了问题。这也是为什么我强调pm.test的第一个参数必须起一个有意义的名字——在几十个接口的测试报告里一眼找到“订单接口返回业务成功”失败绝对比看到一个“test1”失败要快得多。5. 高频故障排查断言脚本飘红的底层原因与修复办法脚本写多了难免踩坑我把最常见的几类问题整理成了一份排查清单遇到报错可以直接对着看。5.1 常见断言报错与修复对照表报错信息原因修复方式There was an error when evaluating the test scriptTests 脚本语法错误或访问了 undefined 的属性检查响应体结构先打印pm.response.json()确认字段Cannot read properties of undefined (reading data)响应体里没有 data 字段直接访问了res.data先做属性存在性判断或使用可选链res?.dataexpected undefined to equal 0目标字段实际值为 undefined确认接口返回的字段名是否写错注意大小写SyntaxError: Unexpected tokenpm.response.json()解析了一个非 JSON 的响应体先用pm.response.text()查看原始内容确认是否 HTML/错误页Expected application/json to include text/html响应头的 Content-Type 不符合预期可能是服务端返回了错误页面检查后端服务是否正常有一个通用排查方法在脚本最上面加一行console.log(pm.response.text())然后打开 Postman 左下角的 Console 面板查看原始响应内容。看到真实返回后再对照断言里的期望值往往一眼就能找出问题。5.2 注意 JSON 对象和数组的嵌套层级接口返回的 JSON 结构有时候嵌套很深常见结构是res.data.list[0].orderInfo.status。写断言时要格外注意每一层的字段名是否正确、是否多了一层或少了一层。我的习惯是先在 Console 面板里用console.log(JSON.stringify(pm.response.json(), null, 2))打印格式化后的 JSON然后照着打印结果一层一层写断言路径。这样能避免凭记忆写错字段也能顺带发现后端多返回了一层包装结构之类的意外情况。5.3 断言命名规范让测试报告可读可用很多人写pm.test第一个参数时很随意什么test1、测试、TT都敢写。等集合里积累了几百条断言跑完一轮批量测试后光是分析报告就能耗费大量时间。断言命名建议遵守一个通用规范动词 对象 预期结果。推荐写法示例登录接口返回HTTP 200登录接口业务状态码为0登录接口返回token非空订单列表每个订单包含orderId这种命名方法的优势在于报告里哪怕不看具体代码也能猜出这条断言在验证什么。多人协作时这个习惯尤其重要。另外一个接口下的断言尽量按“请求层面 → 业务层面 → 数据层面”的次序排列和日常接口测试的思路保持一致。5.4 批量执行与 CI 集成时的断言策略Postman 的断言不只是单个请求时手动点 Send 用来“看看”它的更大价值体现在 Collection Runner 和 Newman 命令行工具中。Collection Runner 可以将整个集合按顺序执行并把所有断言结果自动汇总Newman 则可以把相同的能力带到 Jenkins 等 CI 环境中实现接口测试的自动化回归。从个人的实用经验看批量执行时要特别注意断言之间的“前置依赖”。比如第 1 个请求负责登录并保存 token第 2 个请求依赖这个 token。如果第 1 个请求失败导致 token 没有保存第 2 个请求很大概率也会失败最终报告里会出现一长串红色。这种情况下的处理办法是给前置请求的关键断言做好命名标记跑完后先看前置请求的结果再判断后续请求的失败是独立问题还是连锁反应。还能在后续请求的 Tests 脚本里对 token 是否存在做前置校验如果 token 不存在直接用pm.test.skip跳过该请求的用例避免无意义的失败报告。还有一个细节在 Newman 集成到 CI 时默认情况下只要有一条断言失败命令返回的退出码就是非 0。这对流水线来说是好事能阻断异常发版。但要注意如果接口本身存在“偶发超时”的情况最好在脚本里预留重试机制或者使用 Postman 的setTimeout搭配循环请求实现简单重试否则 CI 会因为偶发问题频繁闪红维护成本很高。6. 多写点、多想点Postman 断言之外还能做些什么接口测试做到后面单一的请求断言只是基础能力。断言结果能不能服务到团队、能不能沉淀成可用资产才是测试工作真正产生价值的地方。就我个人经验来说有两件事收益很高一是把核心接口的断言写到足够细做到“业务字段间的逻辑关系也覆盖到”二是把断言命名的规范和集合的组织结构同步推广到团队里让所有人都能在 10 分钟内看懂别人写的测试脚本。另外在日常调试时不要只盯着绿勾看偶尔故意把断言值改成错误的值确认它会红这也是一种验证断言有效性的好办法。毕竟如果断言永远不会失败那它跟没写也没区别。
返回列表