Postman 学习笔记 II:测试、断言与变量管理
Postman 学习笔记 II:测试、断言与变量管理
上一篇笔记地址在:Postman 学习笔记 I:基础操作与请求管理
这一篇会涉及到一些简单的测试的功能,具体的 repo 还是在下面:
https://github.com/GoldenaArcher/postman-api-study
GH 中里面补了一些 collection 的东西,然后修改了一下 swagger 文档
目前 postman 系列的笔记都写完了,现在正在慢慢整理中,同时再重新进行 GraphQL 的学习——这一会儿会以一个比较工程化的角度去进行学习
测试
其实 UI 还是有一些修改的,现在 pre-req 和 post-req 都放在了 scripts 下面:
之前则是分开来的,另一个可以注意一下的是 console 部分,这里可以展开,这样请求看的就比较直接明显:
⚠️:postman 的所有请求都是共用一个 console 的,即 request1 发送完后,会在 console 输出请求的 URL,request2 发送完后,会在同一个 console 中输出结果
snippet
这算是 postman 本身的优点吧,它有一个快捷键可以一键调出一些比较常用的测试脚本:
如:
pm.test("Status code is 200", function () {pm.response.to.have.status(200);
});
这样可以做一个基础测试,搭配其他的脚本写起来比较方便。特别是在跑 runner 的时候,大批量 API 测试失败时,可以比较直观的看到是 endpoint 失效——即 status code 测试都失败,而不是其他原因
pm.test("Status code is 200", function () {pm.response.to.have.status(200);
});// 真正的测试
pm.test("Status value is ok", function () {var jsonData = pm.response.json();pm.expect(jsonData.status).to.eql("ok");
});
效果如下:
⚠️:postman 中拿到的数据还是字符串,所以需要用 pm.response.json()
转换成 JS 对象
其他的语法
一些比较常见的 code snippets:
const response = pm.response.json();pm.test("Status is OK", () => {pm.expect(response.status).to.be.oneOf(["ok", "OK"]);
});pm.test("Response is an object", () => {pm.expect(response).to.be.an("object");
});pm.test("Product name", () => {pm.expect(response.name).to.be.a("string");
});pm.test("Product in stock", () => {pm.expect(response.inStock).to.be.true;
});pm.test("Product price", () => {pm.expect(response.price).to.be.a("number");pm.expect(response.price).to.be.greaterThan(0);
});
处理错误案例
⚠️:这里用 mock server 的话,需要把 prefer: code=401
加到 header 获取 error case
有些时候需要处理 error cases,比如说故意不传 access token 去验证 auth,或者是传错误的参数去测试后端数据,这时候后端会传回非 200 的 code,这时候可以用下面的方法去测试这种 error cases:
pm.test("Status code is 401", function () {pm.response.to.have.status(401);
});
跑出来的结果是成功的:
collections 中其实也可以写测试,页面布局和 endpoint 是一样的,如果所有的 cases 返回的结果都类似——如都是成功或是失败,必须要登录,包含对应的 headers 等,那么其实可以把对应的 code snippets 放到 collections 中,而不是在每个测试中重复相同的代码
to.have
hack
有的时候去要做 partial string match——这种情况下可以使用 .to.have.string()
去做 partial match,比如:
const response = pm.response.json();pm.test("Status code is 400", () => {pm.expect(response.code).to.eql(400);
});pm.test("Error message", () => {pm.expect(response.message).to.have.string("Invalid category. Must be one of");
});
这样可以检测 query param 传的是不合法的值,但是并不需要做完整的匹配,这样也不用担心后端 enum 的变化
断言及比较
上面提到了 to.have
hack,这个语法看起来有点眼熟……?是的,因为本质上来说 postman 内部使用了一些 chai 的功能,也因此它暴露了一些这样的语法让开发/测试使用。除了 chai 之外,postman 同样也包含了 lodash,因此,下面的功能就能够比较轻松的实现了:
-
深全等判断
这里用的是 lodash 的isEqual
和 chai 的deep.equal
去做对比const lodash = require("lodash");const actual = pm.response.json(); const expected = [{id: 4643,category: "coffee",name: "Starbucks Coffee Variety Pack, 100% Arabica",inStock: true,price: 29.99,},{id: 1234,category: "bread-bakery",name: "Freshly Baked Croissant",inStock: true,price: 3.99,}, ];pm.test("deep equal (chai)", () => {pm.expect(actual).to.deep.equal(expected); });pm.test("deep equal (lodash)", () => {pm.expect(lodash.isEqual(actual, expected)).to.be.true; });
-
部分匹配
这种情况适合做部分匹配,如你知道数据库中包含数据 A,但是因为数据库的变动,db id,updatedAt, updatedBy 这种数据可能会变化
这种情况下就比较适合做部分匹配,而不是深等const lodash = require("lodash");const items = pm.response.json();const mustHave = {category: "bread-bakery",name: "Freshly Baked Croissant", };pm.test("array contains an item matching subset", () => {const matched = _.some(items, (it) => lodash.isMatch(it, mustHave));pm.expect(matched, "should contain required subset").to.be.true; });
-
数组比较
-
嵌套对象比较
-
…
总体来说,如果可以搭配 lodash 和 chai 使用的话,postman 的测试也可以说是进行得比较深入了。只不过受限于工具的使用,代码复用会变得比较的困难
documentation
docs 的页面如下:
这部分的内容我其实用的不是很多,之前和现在的两个项目用的都是 swagger,postman 一直以来都是用来 ping 后端数据的
目前学习到的部分是:
- description 可以用 markdown 编写
- 自动生成请求参数,如 headers,path parameters
- 可以生成自动 docs
这个部分比较 tricky,要在 collection 首页进入:
随后查看:
这个文档还可以 publish,不过这个功能我没怎么研究过…… - 可以自动生成 specifications
变量
这里的变量指的是 postman 的变量,即存在于 global、collection、environment、local 中的变量
下面的设置、获取、清理只需要替换对应的 scope 即可,除了 local,local 层直接使用 pm.variables.method()
去进行管理
postman 的权重为:local > data > environment > collection > global,其他的几个比较直观,data 暂时没有涉及到,这是来自 Runner / Newman 运行时传入的 CSV/JSON 数据,这要在自动化和 batch 操作的时候才会提到了
设置变量
语法如下:
pm.collectionVariables.set("firstName", "test");
其中 firstName
为变量名, test
为值
⚠️:这个操作实际上会修改 collection 中的变量,而 collection 中的变量一旦被修改了,则是会影响所有使用当前变量的成员。换言之,以 accessToken
为例,如果多个用户都在同时使用 postman,那么登录功能就会受到影响,特别是 accessToken
与 role 深度绑定的情况
这种情况多发生在 multi-tenant 和/或 多权限管理 的情况,如,成员 A 需要测试商家 A,成员 B 需要测试商家 B;或成员 A 需要测试管理员,成员 B 需要测试普通权限等
🧐 目前来说,只是针对 postman 这个工具而言,并没有什么特别好的解决方案。使用 pm.variables
将数据存在无法进行跨 API 的交流——这也是常规的流程,在登陆后使用 accessToken
去访问其他 API。使用 collections/environment 就会造成污染,使用 global 则很难做到同步和统一管理。目前来说可用的解决方案就是每个成员 fork 一下 collection,然后使用自己的独立环境进行开发,再定期监控主 collection 的变化,多次 fork 了……
讲了一些 downside,这里说下具体的使用方法——除了 accessToken
的用法:
const response = pm.response.json();pm.test("At least one available product exists", () => {pm.expect(response).to.be.an("array");pm.expect(response.length).to.be.greaterThan(0);pm.collectionVariables.set("productId", response[0].id);
});
这种实现的优势主要有:
-
可以自动获取现存的
productId
,不用担心数据库中 id 的变化导致后续测试失败 -
使用
array
,并对其长度进行断言这种情况下,当 products 没有返回任何的数据,从测试中就能直观地判断出来。如果直接使用
pm.collectionVariables.set('productId', response[0].id);
的话,当 products 的长度为 0 时,下一步测试 single product 就会因为null
值报错。那个时候再去判断哪里出了问题,就比较费时费力 -
测试可以比较方便的进行 extend,如这里只是对 array 和长度进行了断言。如果有需要的话,也可以对 product 进行断言,测试数据结构是否有变化
不过类似操作,写在 collections 中会比较方便进行管理
获取变量
调用的方法也很简单,将 set
换成 get
即可:
pm.test("Correct product was retrieved", () => {pm.expect(response.id).to.equal(pm.collectionVariables.get("productId"));
});
清空变量
这里的清空会直接清空整个 scope 中的所有变量,所以需要慎用,语法为:pm.scope.unset('varName')
,如 pm.collectionVariables.unset('varName')
environment
environment 是一个可以更加动态的管理变量的 scope,搭配和 collection/workspaces 使用,可以统一/固定/规范化不同的环境,但是仍旧使用相同的变量
UI 操作如下:
环境是独立于 collection/workspace 的管理,因此可以单独进行 import/export——如果要使用不同的环境,如 local/dev/staging/prod 这种,也需要单独进行导入导出,它不与 collection/workspace 进行绑定
cookies
这里主要讲一下获取方式,即 pm.cookies.get("session_id")
,这个搭配断言比较好用
set cookie 比较的特殊,语法如下:
pm.cookies.jar().set(pm.request.url, "session_id", "mock-123456");
在使用 cookie 之前,需要将网址加入 allowlist 中,官方文档参考地址在:add domains to the allowlist,如果没有加入到 allowlist 中会出现访问失败的情况——我也遇到过这种情况,debug 了很久……
headers
headers 分为 request 和 response,调用方式为 pm.request.headers
和 pm.response.headers
,除了 set
和 get
之外,还有 has
这个调用方式
这个用的还是比较少的,request headers 可能会用的稍微多一些——这个值可以在 pre-script 的阶段进行修改