《API Testing and Development with Postman》最新第二版封面
文章目录
前言
本篇为原书第六章自学笔记的上篇。因为本章内容实在过于炸裂,且我在实测环节发现很多 Bug,特此跳过前面的基础知识,直接来到这一章帮作者修订一下书中存在的问题。至于跳过的第 3 至 5 章,等更新完这章内容再另行补上。特此说明。
第六章 Postman 测试脚本的创建
本章概要
- 检查 API 接口响应
- 设置预请求脚本(pre-request scripts)
- Postman 环境(environments)的用法
6.0 概述
如果某个测试脚本运行了很长时间都从没出现过故障,那么不排除脚本本身在写法上可能有问题,例如一些低级断言错误:
true === true
、
5 === 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 的示例代码:
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
中的签名为:
test(testName:string, specFunction:Function):void
其中
specFunction
是一个异步函数,本质上是一个返回逻辑
true
或
false
的条件判定函数(也称
predicate
谓词函数)。虽然实测发现,令回调函数返回一个非逻辑值也不会报错,但最好还是按惯例返回逻辑值:
【图 6.3 实测回调函数返回值类型:返回一个非逻辑值也不报错,但最好返回逻辑值】
6.1.2 关于 Chai 断言
Postman
中的断言是基于断言库
Chai.js
(
Chai
读作
/ʧaɪ/
,同英文单词 chai 的发音,表示“茶叶”)做的二次封装,
Chai.js
是一款同时支持
TDD
风格和
BDD
风格的断言库,其名称来自一种印度茶,暗含提供一种友好易用的测试体验的意思。因此刚才的断言逻辑也可以用
Chai.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"
:
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
结果,得到如下模板:
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 是否正确:
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
pm.test("Content-Type is present",function(){
pm.response.to.have.header("Content-Type");});
这里和最开始校验响应码的写法很像。这也是 BDD 风格的优势:声明式语法,贴近自然语言。
6.4 Postman 常见的断言写法
6.4.1 常见自定义断言对象
Postman
在
Chai.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
- 校验 body 是否存在
- 校验 body 文本是否完整匹配期望内容
- 校验 body 是否匹配某正则表达式
- 校验 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
- 是否为 JSON 格式的响应 body
- 是否与给定 JSON 匹配
pm.response.to.have.jsonBody;
pm.response.to.have.jsonBody({key: value});
.responseTime
- 响应时间精确匹配给定毫秒时间
- 响应时间不少于给定毫秒时间
- 响应时间不超过给定毫秒时间
- 响应时间介于给定时间范围内(单位: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
- 响应内容尺寸精确匹配给定字节大小
- 响应内容尺寸不少于给定字节大小
- 响应内容尺寸不超过给定字节大小
- 响应内容尺寸介于给定字节范围内(单位:字节)
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);
。
关于断言结果取反
可以在上述断言中添加
.not
让断言逻辑取反。例如:
pm.response.to.not.have.jsonSchema(mySchema);// 或者 pm.response.to.have.not.jsonSchema(mySchema);
.not
只要加在助动词(
to
)或动词(
have
)后,取反逻辑就成立。
6.4.2 实测情况
(1)实测
.body
对象:
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
对象:
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 提示的方法签名,分别传入
key
和
value
的值作为参数。这里顺便分享一个小技巧,可以很方便的查看 Postman 方法的内置签名:书写脚本时,Postman 会自动提示当前对象的方法签名,遇到存在多个签名的情况,可以使用 Alt + Up 或 Alt + Down 进行翻页,查看隐藏的其他签名。例如
pm.response.to.have.jsonBody()
方法的签名就有四个,如下图所示:
【图 6.8.1 pm.response.to.have.jsonBody() 方法存在四种签名】
// Signature 1jsonBody():any// Signature 2jsonBody(optionalExpectEqual:any):any// Signature 3jsonBody(optionalExpectPath:string):any// Signature 4jsonBody(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
下的内置对象,实测脚本如下:
// 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']};const tv4 =require('tv4');
pm.test('Response is a valid JSON (via tv4)',function(){
pm.expect(tv4.validate(json1, schema)).to.be.true;});const Ajv =require('ajv');const ajv =newAjv();
pm.test('Response is a valid JSON (via Ajv)',function(){
pm.response.to.have.jsonSchema(schema);
pm.expect(ajv.validate(schema, json1)).to.be.true;});
运行结果:
【图 6.8 校验响应 JSON 的 schema 模式时,Postman 新旧两版写法的实测结果对比截图】
(4)本节给出的三个练手测试实战
(4.1)检查服务器是否为 nginx(该信息位于 Header 中):
pm.test('The server is nginx',()=>{const server = pm.response.headers.get('Server');
pm.expect(server).to.contain('nginx');});
实测结果:
【图 6.9 实测从 Header 中读取信息、校验服务器是否为 nginx 的运行结果】
(4.2)检查请求响应时间是否在 500ms 内:
pm.test('The response time for this call is less than 500ms',()=>{
pm.response.to.have.responseTime.below(500,'responseTime too long');});
实测结果:
【图 6.10 可根据 Chai.js 相关文档,结合 Postman 自动提示,自定义报错提示内容】
(4.3)检查 Luke 是否出演过当中的 4 部电影:
pm.test('Luke appears in 4 films',()=>{const{ films }= pm.response.json();
pm.expect(films).lengthOf(4);});/* "films": [
"https://swapi.dev/api/films/1/",
"https://swapi.dev/api/films/2/",
"https://swapi.dev/api/films/3/",
"https://swapi.dev/api/films/6/"
],*/
实测结果:
【图 6.11 根据 Postman 自动提示校验响应 JSON 中的某数组字段的长度情况】
由于本章内容较多,6.4 小节后面的剩余内容放到下篇介绍,敬请期待!
版权归原作者 安冬的码畜日常 所有, 如有侵权,请联系我们删除。