【玩转 Postman 接口测试与开发2_005】第六章:Postman 测试脚本的创建(上)

《API Testing and Development with Postman》最新第二版封面

文章目录

  • [第六章 Postman 测试脚本的创建](#第六章 Postman 测试脚本的创建)
    • [6.0 概述](#6.0 概述)
    • [6.1 检查 API 接口响应](#6.1 检查 API 接口响应)
      • [6.1.1 关于 pm.test 方法](#6.1.1 关于 pm.test 方法)
      • [6.1.2 关于 Chai 断言](#6.1.2 关于 Chai 断言)
    • [6.2 检查响应的 body 部分](#6.2 检查响应的 body 部分)
    • [6.3 检查请求头 Header](#6.3 检查请求头 Header)
    • [6.4 Postman 常见的断言写法](#6.4 Postman 常见的断言写法)
      • [6.4.1 常见自定义断言对象](#6.4.1 常见自定义断言对象)
      • [6.4.2 实测情况](#6.4.2 实测情况)

前言

本篇为原书第六章自学笔记的上篇。因为本章内容实在过于炸裂,且我在实测环节发现很多 Bug,特此跳过前面的基础知识,直接来到这一章帮作者修订一下书中存在的问题。至于跳过的第 3 至 5 章,等更新完这章内容再另行补上。特此说明。

第六章 Postman 测试脚本的创建

本章概要

  • 检查 API 接口响应
  • 设置预请求脚本(pre-request scripts)
  • Postman 环境(environments)的用法

6.0 概述

如果某个测试脚本运行了很长时间都从没出现过故障,那么不排除脚本本身在写法上可能有问题,例如一些低级断言错误:true === true5 === 5 等。

设计良好的测试套件也可能因为糟糕的断言脚本而功败垂成。

所谓 断言(assertion),即测试中检查实际结果是否符合预期的校验逻辑。

Postman 的测试脚本是基于 JavaScript 实现的,并内置了 Chai.js 断言库,可以对请求的 Header 头信息及 body 正文进行断言校验。

本章介绍这些断言的基本写法与用法,并结合 Collection Runner 工具介绍在测试套件层面(即 Collection 集合)自动运行多个请求的具体操作。

相关资源链接:详见 GitHub 第六章文件夹

6.1 检查 API 接口响应

示例项目:电影《星球大战》API 接口(集合名称:Star Wars API -- Chapter 6)。

相关 JSON 配置文件 Star Wars API_Chapter6_initial.postman_collection.json 详见百度网盘:

  • 链接地址:https://pan.baidu.com/s/1cbc8hFxzi2g08egDVU8XAw
  • 提取码:yo47

导入配置后,点击 Postman 右上角的 Variables 图标查看变量声明情况,会看到预先定义的基础 URL 变量 base_url

【图 6.1 导入演示项目后看到的预定义变量 base_url】

然后将初始请求 Get People 的 URL 由 https://swapi.dev/api/people/1 替换为 {``{base_url}}/people/1 就完成了环境搭建。

接着找到 Get People 下的 Scripts 标签,选中左边的 Post-response 选项卡,在脚本编辑区插入一段 Postman 内置的测试用例,例如验证响应码为 200 的示例代码:

js 复制代码
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

实测执行情况如下(左下角表示断言已通过):

【图 6.2 执行修改后的 Get People 请求得到的响应结果与测试结果截图】

6.1.1 关于 pm.test 方法

测试脚本中的 pm.test 方法,即与绝大多数测试框架中的 test 方法用法一致,其中的 pm 指代 Postman,是一个全局对象,用于访问 请求响应 中的数据,或者访问 变量Cookie 等。而 test 方法接收一个描述信息字符串和一个回调函数;前者用于说明测试用例的意图,后者为具体的测试校验逻辑。pm.test() 方法在 Postman 中的签名为:

ts 复制代码
test(testName: string, specFunction: Function): void

其中 specFunction 是一个异步函数,本质上是一个返回逻辑 truefalse 的条件判定函数(也称 predicate 谓词函数)。虽然实测发现,令回调函数返回一个非逻辑值也不会报错,但最好还是按惯例返回逻辑值:

【图 6.3 实测回调函数返回值类型:返回一个非逻辑值也不报错,但最好返回逻辑值】

6.1.2 关于 Chai 断言

Postman 中的断言是基于断言库 Chai.jsChai 读作 /ʧaɪ/,同英文单词 chai 的发音,表示"茶叶")做的二次封装,Chai.js 是一款同时支持 TDD 风格和 BDD 风格的断言库,其名称来自一种印度茶,暗含提供一种友好易用的测试体验的意思。因此刚才的断言逻辑也可以用 Chai.js 重构为:

js 复制代码
const chai = require('chai');
pm.test("Status code is 200", function () {
    // pm.response.to.have.status(200);
    chai.expect(pm.response.code).equals(200);
});

实测结果:

【图 6.4 在 Postman 中使用 chai 断言重写校验逻辑】

6.2 检查响应的 body 部分

如果从 Postman 提供的示例脚本中选择 Response body: Contains string,则可以校验响应 body 中是否包含字符串,例如 "skin_color"

js 复制代码
pm.test("Body matches string 'skin_color'", function () {
   pm.expect(pm.response.text()).to.include("skin_color");
});

这里的 pm.response.text() 返回字符串形式的响应 body 内容。

如果选择示例代码 Response body:JSON value check,则可以校验请求响应的 JSON 结果,得到如下模板:

js 复制代码
pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
});

从第 2 行可见,Postman 的示例脚本还是用的 ES5 语法。这里也可以改为 ES6 的写法。关键是无需从零开始手动构建测试脚本。

此外,回调函数还支持 console.log() 控制台输出,方便调试测试脚本。控制台标签 console 位于主界面的左下角位置。

实测:Luke 的家园 URL 是否正确:

js 复制代码
pm.test("Check the homeworld URL", function () {
    const { homeworld } = pm.response.json();
    const expected = 'https://swapi.dev/api/planets/1/';
    pm.expect(homeworld).to.eql(expected);
});

运行结果(书中放到 GitHub 的参考文件是错的,期望值写的是 http,新版响应中为 https):

【图 6.5 实测响应 body 为 JSON 数据时,校验 homeworld 字段的值是否正确】

6.3 检查请求头 Header

Postman 示例代码:Response header: Content-Type header check

js 复制代码
pm.test("Content-Type is present", function () {
    pm.response.to.have.header("Content-Type");
});

这里和最开始校验响应码的写法很像。这也是 BDD 风格的优势:声明式语法,贴近自然语言。

6.4 Postman 常见的断言写法

6.4.1 常见自定义断言对象

PostmanChai.js 的基础上内置了几个常用的自定义断言对象,例如 pm.response.to.have.status 中的 .status,以及检查 Header 时用到的 pm.response.to.have.header 中的 .header

常用的自定义断言对象梳理如下:

自定义对象 功能描述 断言示例
.statusCode 校验响应码的取值 pm.response.to.have.statusCode(200);
.statusCodeClass 校验响应码所属类别:2 表示以 2 开头的响应码,3 表示以 3 开头的响应码,以此类推 pm.response.to.have.statusCodeClass(2);
.statusReason 校验响应码描述信息,它是与响应码对应的固定文字描述信息,例如 200 对应 OK pm.response.to.have.statusReason('OK');
.status 校验响应码或对应描述信息 pm.response.to.have.status(200); pm.response.to.have.status("Created");
.header 校验请求头是否存在,或者请求头的键值对是否正确 pm.response.to.have.header("Content-Type"); pm.response.to.have.header("Content-Type", "application/json;");
.withBody 校验响应是否包含 body pm.response.to.be.withBody;
.json 检查 body 是否为 JSON 格式 pm.response.to.be.json;
.body 1. 校验 body 是否存在 2. 校验 body 文本是否完整匹配期望内容 3. 校验 body 是否匹配某正则表达式 4. 校验 body 是否与给定 JSON 匹配 pm.response.to.have.body; pm.response.to.have.body("JSON text"); pm.response.to.have.body(<regex>); pm.response.to.have.body({key1:value1,...});
.jsonBody 1. 是否为 JSON 格式的响应 body 2. 是否与给定 JSON 匹配 ^[1](#自定义对象 功能描述 断言示例 .statusCode 校验响应码的取值 pm.response.to.have.statusCode(200); .statusCodeClass 校验响应码所属类别:2 表示以 2 开头的响应码,3 表示以 3 开头的响应码,以此类推 pm.response.to.have.statusCodeClass(2); .statusReason 校验响应码描述信息,它是与响应码对应的固定文字描述信息,例如 200 对应 OK pm.response.to.have.statusReason('OK'); .status 校验响应码或对应描述信息 pm.response.to.have.status(200); pm.response.to.have.status("Created"); .header 校验请求头是否存在,或者请求头的键值对是否正确 pm.response.to.have.header("Content-Type"); pm.response.to.have.header("Content-Type", "application/json;"); .withBody 校验响应是否包含 body pm.response.to.be.withBody; .json 检查 body 是否为 JSON 格式 pm.response.to.be.json; .body 1. 校验 body 是否存在 2. 校验 body 文本是否完整匹配期望内容 3. 校验 body 是否匹配某正则表达式 4. 校验 body 是否与给定 JSON 匹配 pm.response.to.have.body; pm.response.to.have.body("JSON text"); pm.response.to.have.body(<regex>); pm.response.to.have.body({key1:value1,...}); .jsonBody 1. 是否为 JSON 格式的响应 body 2. 是否与给定 JSON 匹配 1 pm.response.to.have.jsonBody; pm.response.to.have.jsonBody({key: value}); .responseTime 1. 响应时间精确匹配给定毫秒时间 2. 响应时间不少于给定毫秒时间 3. 响应时间不超过给定毫秒时间 4. 响应时间介于给定时间范围内(单位:ms) pm.response.to.have.responseTime(150); pm.response.to.have.responseTime.above(150); pm.response.to.have.responseTime.below(150); pm.response.to.have.responseTime.within(100,150); .responseSize 1. 响应内容尺寸精确匹配给定字节大小 2. 响应内容尺寸不少于给定字节大小 3. 响应内容尺寸不超过给定字节大小 4. 响应内容尺寸介于给定字节范围内(单位:字节) pm.response.to.have.responseSize(50); pm.response.to.have.responseSize.above(50); pm.response.to.have.responseSize.below(100); pm.response.to.have.responseSize.within(50,100); .jsonSchema 校验响应的 JSON 格式的 body 是否与给定 schema 模式匹配 pm.response.to.have.jsonSchema(mySchema);)^ pm.response.to.have.jsonBody; pm.response.to.have.jsonBody({key: value});
.responseTime 1. 响应时间精确匹配给定毫秒时间 2. 响应时间不少于给定毫秒时间 3. 响应时间不超过给定毫秒时间 4. 响应时间介于给定时间范围内(单位:ms) pm.response.to.have.responseTime(150); pm.response.to.have.responseTime.above(150); pm.response.to.have.responseTime.below(150); pm.response.to.have.responseTime.within(100,150);
.responseSize 1. 响应内容尺寸精确匹配给定字节大小 2. 响应内容尺寸不少于给定字节大小 3. 响应内容尺寸不超过给定字节大小 4. 响应内容尺寸介于给定字节范围内(单位:字节) pm.response.to.have.responseSize(50); pm.response.to.have.responseSize.above(50); pm.response.to.have.responseSize.below(100); pm.response.to.have.responseSize.within(50,100);
.jsonSchema 校验响应的 JSON 格式的 body 是否与给定 schema 模式匹配 pm.response.to.have.jsonSchema(mySchema);

注意:原书 .jsonBody 中的第二个写法是错的,这里为了照顾下一节的实测环节,暂时保留错误写法。正确写法应该是:pm.response.to.have.jsonBody(key, value);

6.4.2 实测情况

(1)实测 .body 对象:

js 复制代码
pm.test('The response contains body', () => {
    pm.response.to.have.body;
    pm.response.to.have.body();
});

pm.test('The response body equals expected string', () => {
    const bodyText = pm.response.text();
    pm.response.to.have.body(bodyText);
});

pm.test('The response body matches some RegExp', () => {
    pm.response.to.have.body(/skin_color/i);
});

pm.test('The response body matches expected JSON object', () => {
    const json = pm.response.json();
    pm.response.to.have.body(json);
});

运行结果(匹配文本和 JSON 对象时 必须完全匹配):

【图 6.6 实测自定义断言对象 .body 结果截图】

(2)实测 .jsonBody 对象:

js 复制代码
pm.test('The response has a JSON-format body', () => {
    pm.response.to.have.jsonBody;
    pm.response.to.have.jsonBody();
});

pm.test('The response JSON match some key-value pair', () => {
    // Error
    pm.response.to.have.jsonBody({
        'films[2]': 'https://swapi.dev/api/films/3/aaabbbccc'
    });
    // Correct
    pm.response.to.have.jsonBody(
        'films[2]', 'https://swapi.dev/api/films/3/'
    );
});

运行结果:

【图 6.7 实测自定义对象 jsonBody 的写法运行结果截图。书中所给的第二个写法是错的,参数不能为一个 JSON 对象】

注意!!!

根据实测结果,校验响应 JSON 中是否包含给定的键值对,不能直接传入一个 JS 对象,而应该按 Postman 提示的方法签名,分别传入 keyvalue 的值作为参数。这里顺便分享一个小技巧,可以很方便的查看 Postman 方法的内置签名:书写脚本时,Postman 会自动提示当前对象的方法签名,遇到存在多个签名的情况,可以使用 Alt + UpAlt + Down 进行翻页,查看隐藏的其他签名。例如 pm.response.to.have.jsonBody() 方法的签名就有四个,如下图所示:

【图 6.8.1 pm.response.to.have.jsonBody() 方法存在四种签名】

ts 复制代码
// Signature 1
jsonBody(): any
// Signature 2
jsonBody(optionalExpectEqual: any): any
// Signature 3
jsonBody(optionalExpectPath: string): any
// Signature 4
jsonBody(optionalExpectPath: string, optionalValue: any): any

根据实测的上下文,这里显然应该使用第四种签名写法。

(3)实测 .jsonSchema 对象:

校验响应中的 JSON 结构时,旧版 Postman 使用 tv4 模块(tv4 全称 Tiny Validator for JSON Schema version 4);新版中该写法已经废弃,推荐写法为上述列表中的写法,即改用 Ajv 依赖(全称 Another JSON Schema Validator)并作为 pm.response 下的内置对象,实测脚本如下:

js 复制代码
// fictional results from pm.response.json();
const json1 = {
    "args": {},
    "films": [],
    "files": {},
    "form": {
        "foo1": "bar1",
        "foo2": "bar2"
    },
    "headers": {},
    "json": null,
    "url": "https://postman-echo.com/post"
};

const schema = {
    type: 'object',
    properties: {
        films: {type: 'array'},
        url: {type: 'string'},
    },
    required: [
        'films', 'url'
    ]
};

pm.test('Response is a valid JSON (via tv4)', function() {
    pm.expect(tv4.validate(json1, schema)).to.be.true;
});

pm.test('Response is a valid JSON (via Ajv)', function() {
    pm.response.to.have.jsonSchema(schema);
});

运行结果:

【图 6.8 校验响应 JSON 的 schema 模式时,Postman 新旧两版写法的实测结果对比截图】

由于本章内容较多,6.4 小节后面的剩余内容放到下篇介绍,敬请期待!


  1. 原书这里的写法有误。根据最新版 Postmanv11.18.2 )的实测结果,期望的键值对应该分别作为参数传入 jsonBody() 函数内,具体详见下一节第二个实测案例。 ↩︎
相关推荐
刚刚好ā1 分钟前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
yqcoder1 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。2 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客2 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
Domain-zhuo3 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小丁爱养花3 小时前
前端三剑客(三):JavaScript
开发语言·前端·javascript
码农六六3 小时前
vue3封装Element Plus table表格组件
javascript·vue.js·elementui
徐同保3 小时前
el-table 多选改成单选
javascript·vue.js·elementui
快乐小土豆~~3 小时前
el-input绑定点击回车事件意外触发页面刷新
javascript·vue.js·elementui