2017-12 补充:Postman 某次大更新后 API 有了非常多不兼容的改动,这下面有些肯定调不通了
这里假设你对 Postman 比较熟悉
如果之前没关注过,可以看 上一篇
如果不打算用来做自动化测试,以下绝大部分都没必要
对随便在界面发下请求来说太麻烦
假设接口返回 JSON
前提:globals
里存了常用的函数,如:
assertNotTimeout:var hasResponse=postman.getResponseHeader('Content-Type')?true:false; if(!hasResponse) tests['服务端在超时前没返回任何数据,请检查相关服务、网络或反向代理设置(以下跳过其他断言)']=false;
logParams:if(hasResponse) tests[`[INFO] 请求参数(仅限POST,超时没返回时不解析):${JSON.stringify(request.data)}`]=true;
getResponseJson:try{if(hasResponse) var json=JSON.parse(responseBody);}catch(err){ tests['服务端没返回合法的JSON格式,请检查相关服务、网络或反向代理设置(以下跳过其他断言)']=false; tests[`[INFO] 返回:${responseBody}`]=true; console.error(err);}
assertType:var assertType=(name,value,type)=>{let isType=(type==='array')? Array.isArray(value):typeof value===type; tests[`${name}为${type}(实际值:${value})`]=isType;};
assertEqual:var assertEqual=(name,actual,expected)=>{tests[`${name}等于${expected}(实际值:${actual})`]=actual===expected;};
assertNotEqual:var assertNotEqual=(name,actual,expected)=>{tests[`${name}不等于${expected}(实际值:${actual})`]=actual!==expected;};
这块难看不是太大问题,1 个人写好、导出、提交 Git,通知其他人拉、导入,完事
然后就可以一种套路走天下——
开头把函数全拿出来:
// setup
eval(globals.assertNotTimeout); // 判断是否超时
eval(globals.logParams); // 在报告里显示POST请求参数
eval(globals.getResponseJson); // 返回json变量,保存了整个返回的JSON对象
// 定义几个常用函数,参数都是 显示文字、实际值、期望值
eval(globals.assertType);
eval(globals.assertEqual);
eval(globals.assertNotEqual);
假设项目里每个接口必定会带resultCode
属性,成功返回 1,
失败时还会带resultMsg
detailMsg
属性,但未必 2 个都有提示信息
先来个通用的套路,不管接下来漏什么,总不会把最基本的漏了:
// 项目通用的断言
if (json) {
const { resultCode, resultMsg, detailMsg } = json;
assertEqual('resultCode', resultCode, 1);
if (resultMsg) tests[`[INFO] 接口提示信息:${resultMsg}`] = true;
if (detailMsg) tests[`[INFO] 接口提示信息:${detailMsg}`] = true;
}
如果这接口报错也是正常情况(如检查手机号是否已注册的接口,毕竟随机生成的号码有可能跟已有的重复),加上流程控制,反复通过名字调自己,直到得到想要的结果
if (!resultCode || resultCode !== 1) {
tests[`[WARN] 手机号${environment.randomMobile}无法注册,重试中……`] = true;
postman.setNextRequest('验证手机是否注册');
}
如果这接口执行失败会导致之后的接口没有跑的意义,那就中止测试流程
(比如注册失败,接下来需要登录的操作都不用测了)
// (注明中止理由)
if (!resultCode || resultCode !== 1) {
tests['[ERROR] 执行失败,跳过依赖本接口的后续测试'] = false;
postman.setNextRequest(null);
}
在界面/HTML 报告里看到的assertEqual
输出例:resultCode等于1(实际值:1)
有些接口返回内容很简单,不需要做什么验证,到上面就结束了
门槛低到只要会复制粘贴就 “能做自动化测试”(shell 脚本别人写,Jenkins 别人配),还很稳定!
当然,全都像上面这样搞的话会有很多假阴性
很多接口还要断言更多东西
如果返回值需要和环境变量/请求参数/固定的值做对比,与其写得到处都是,将来改起来忘了这忘了那
不如都塞一个对象里,要改一次改完:
// 该接口的断言
const expected = {
mobile: environment.PATIENT_MOBILE,
userType: 1,
};
// 如果值来自写死的请求参数:request.data.变量名
// 如果值来自数据文件:data.变量名
// Postman还是很体贴的,给了你各种实用的全局对象(虽然在文档里藏得很深,虽然那个data极易重名……)
假设项目里大多数接口返回的 JSON 里都有个data
对象,各种业务相关属性都在里面
接下来又一个套路:
if (json && json.data) {
// ...
} else {
tests['返回值包含data对象'] = false;
}
if 里断言什么提取什么就跟具体接口有关了,需要了解业务逻辑、查接口文档、问开发等
但还是有不少套路
首先总要把某些属性取出来放进变量吧(这里用了 ES6 的对象解构)
const { access_token, patientId, userId, login, user } = json.data;
需要放进环境变量传给下个请求用的东西,总得做点断言吧,就算没法断言具体的值,判断类型还是可以的
assertType('令牌', access_token, 'string');
assertType('患者ID', patientId, 'number');
assertType('user', user, 'object');
environment.patientId = patientId;
environment.PATIENT_ACCESS_TOKEN = access_token;
返回的某个对象里如果需要做更精细的断言,继续拆
上面我们定义的expected
对象在这里用上了
if (user) {
const { telephone, userId, userType, } = user;
assertEqual('mobile', telephone, expected.mobile);
assertEqual('userId', userId, json.data.userId);
assertEqual('userType', userType, expected.userType);
}
PS:
专门用难维护的globals
定义函数的意义就在这里,断言数量太多了(上面省略了一些)
如果用官方的写法,想想满屏差不多又有点不同的东西维护起来多恐怖……
tests[`foo为string类型(实际值:${foo})`] = typeof foo === 'string';
tests[`bar等于1(实际值:${bar})`] = bar === expected;
【总结】
写断言的套路不止 1 个,但个人认为这个比较适合小公司/小项目(最底下还有个 JSON Schema 的)
上面的代码块连起来搞成一个可以复制粘贴的模板就能到处用
等以后新版本出来了,支持在集合/文件夹级别定义函数,可能就没必要到处复制粘贴,甚至不需要globals
了
有些接口,如拉消息,返回比提交的时间戳新的数据
// 如果不打算重用,在参数里用Postman的内建变量`{{$timestamp}}`就行,否则:
environment.ts = Date.now();
// 如果不打算重用,在参数里用Postman的内建变量`{{$guid}}`就行,否则:
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/x/g, () => (Math.floor(Math.random() * 16)).toString(16))
.replace(/y/g, () => (Math.floor(Math.random() * 4 + 8)).toString(16));
让请求参数有点变化
// 如果想要0~1000的随机数,且不打算重用,参数里直接用Postman内建变量`{{$randomInt}}`就行
// 否则自己实现:
const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; // 随机整数
const getRandomValue = list => list[randomInt(0, list.length - 1)]; // 随机选项
例:
// 随机手机
environment.randomMobile = `18${randomInt(100000000, 999999999)}`;
// 随机2-6字姓名
const charsInName = ['赵', '钱', '孙', '李', '王', '张'];
const numOfChars = randomInt(2, 6);
let randomName = '';
for (let i = 0; i < numOfChars; i++) {
let index = randomInt(0, 5);
randomName += charsInName[index];
}
environment.randomName = randomName;
// 随机设备token(推送服务商提供)
const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let deviceToken = '';
for (let i = 0; i < 64; i++) {
deviceToken += getRandomValue(chars);
}
environment.randomDeviceToken = deviceToken;
// 随机设备名
environment.randomDevice = getRandomValue(['ios', 'android']);
// 随机行政区划
const divisions = ['北京市', '上海市', '天津市', '重庆市', '广东省 深圳市', '广东省 广州市', '新疆维吾尔自治区 克孜勒苏柯尔克孜自治州'];
environment.randomDivision = getRandomValue(divisions);
// 随机生日(时间戳)
// 假设今天是2017-1-1,距1970-1-1 47年,则生日范围为 1923-1-1 ~ 2017-1-1
environment.randomBirthday = randomInt(0 - Date.now(), Date.now());
// 随机群名
const groupNames = ['犯罪团伙', 'We are gay', '`~!@#$%^&*()-_ =+'];
environment.groupName = getRandomValue(groupNames) + randomInt(0, 1000);
environment.XXX == null || environment.NAME = value;
// == null 匹配 null 和 undefined
// 通常避免 !environment.XXX 或 environment.XXX || ... 的写法,变量有可能是false, '', 0
避免发送请求的速度比数据库更新速度快,造成误报
const sleep = (milliseconds) => {
const start = Date.now();
while (Date.now() <= start + milliseconds) {}
};
// 就是限时的死循环,请用小一点的数字调试
// Postman是用JS写的,单线程异步,主线程被阻塞了就没法做其他操作
用于修改密码接口,2 套密码来回替换
// 假设已设置了环境变量PWD
const oldPwd = environment.PWD;
const newPwd = environment.NEW_PWD;
if (!newPwd || newPwd === oldPwd) {
newPwd = '123456';
} else {
const tmp = oldPwd;
oldPwd = newPwd;
newPwd = tmp;
}
environment.NEW_PWD = newPwd;
environment.PWD = oldPwd;
仅仅因为 Postman 支持tv4,试着在项目用了下,效果不好
// 依然假设返回的JSON里有个字段叫resultCode,1表示成功
// (当时还没想到用globals存函数,中间反序列化JSON那段基本上就是现在的globals.getResponseJson)
const schema = {
// 这里手写/贴上在线工具生成的一长串JSON schema
}
let json;
try {
json = JSON.parse(responseBody);
} catch(err) {
tests['服务端没返回合法的JSON格式,请检查相关服务、网络或反向代理设置(以下跳过其他断言)'] = false;
tests[`[INFO] 返回:${responseBody}`] = true;
console.error(err);
}
if (json) {
const result = tv4.validateResult(json, schema);
tests['JSON Schema格式正确'] = result.valid;
if (result.valid) {
tests.isSuccess = json.resultCode === 1;
if (tests.isSuccess) {
// ...
}
} else {
console.error(result.error);
console.error(responseBody);
}
}
JSON Schema 可用 这网站 生成,把返回的 JSON 字符串贴进去,点Generate Schema(通常默认选项就够用了)
- 注意不要复制
"$schema":
那行,Postman 不支持引用外部模板- 按默认参数,会把贴进去的 JSON 里的所有字段都认为是必须的。如果某些返回字段是可选的,找到相应的
"required":
数组,去掉那字段