四、JMeter
1. 接口是系统向外部暴露的交互方式,
2. 接口可以接受来自用户的数据,返回来自系统内部的数据
3. 负责数据的校验、加工、存储、权限控制
4. 是系统内、外的分界线例子:
电脑(系统)上面的各种插口(接口)
电路板上的金属焊点(接口)
12306上各种订票方式(接口)
1. 接口测试通过,能保证前端、app、各样功能顺利对接(必要的流程)
2. 接口出现BUG, 会比前端BUG更加严重、影响范围更大(减低风险)
3. 接口测试效率会比UI测试效率高,便于测试,收益更大(提升效率)
1. 模拟软件(需要工具)
2. 对接口进行数据收发(需要专业的工具)
3. 对结果进行判断(支持灵活的断言)
1. JMeter: 开元软件,支持多种接口类型、支持接口测试和性能测试,开元进行二次扩展
2. Postman: 收费软件, 简单、方便,适合开发临时调试接口, 需要联网使用
3. APIFOX 等新兴工具: 市场占比比较小,不是主流,适合小团队尝鲜
2.1 JMeter 安装配置
下载地址: https://www.oracle.com/java/technologies/downloads/#java17
配置方式: https://www.cnblogs.com/wangyong123/articles/17844518.html#_lab2_2_1
安装结束,记住安装;路径, 双击运行
(Windows)apache-jmeter-5.6.3\bin\jmeter.bat
(Linux)apache-jmeter-5.6.3\bin\jmeter出现如下界面 则 安装成功
添加jmeter启动命令: 环境变量的方式 D:\software\apache-jmeter-5.6.3\bin 加入到 path
通过 cmd的jmeter命令就可以启动jmeter了
目录:
bin:二进制,存放执行的文件、命令以及配置文件
docs:文档,内部的API文档
extras: 额外的,和其他工具进行对接
lib: 库
lib/: java的依赖
lib/ext:JMeter依赖、插件
printable_docs: 可打印文档,使用说明核心文件:
bin/jmeter: 启动文件
bin/jmeter.properties: 配置文件
lib: 第三方库
三、JMeter系统结构
3.1 元件类型
测试计划: 容器
线程组: 干活的人
取样器: 要干的活
1. 逻辑控制器: 对取样器进行逻辑控制、判断、循环
2. 定时器: 让取样器、延迟一段时间后再执行
3. 监听器: 记录和展示取样器的执行结果
4. 断言: 判断取样器的执行结果
5. 配置元件: 配置、修改取样器的内容
6. 前置处理器: 取样器执行之前,自动执行
7. 后置处理器: 取样器执行之后,自动执行
3.2 元件类型操作练习(请求百度)
3.2.1 请求操作
1. 添加线程组->取样器-> HTTP请求
2. 输入访问百度的协议、服务器名称或者IP、端口号
3. 运行元件
1. 线程组->监听器->查看结果树
2. 运行元件
3. 查看结果
1. 线程组->逻辑监听器->循环控制器
2. 将HTTP请求和查看结果树移动到循环控制器里面(代表里面循环三次)
3. 输入循环次数为 3
4. 清空原本的结构树内容,重新运行,发现请求次数变为 3
1. 循环控制器添加 -> 前置处理器 -> Be按Shell...
2. 输入内容 log.info("HTTP请求访问百度"); java语言,需要符合java规范
3. 运行发现出现三次打印 HTTP请求访问百度
1. 加载配置文件
2. 加载线程组
3. 加载取样器
4. 为取样器加载各种复杂元件(比如循环控制器)
1. 先父级元件
2. 其次同级元件
3. 再后子级元件
4. 不加载旁系元件
修改项目工程目录如下:
其中父级元件、同级元件、子级元件为 后置处理器,中间加入打印信息 log.info("..."),...为当前元件名称加载顺序是:
加载线程组 ->
加载取样器: -- HTTP请求A
加载元件 : -- 父级元件
-- 同级元件
-- HTTP请求A子级元件
加载取样器: -- HTTP请求B
加载元件 : -- 父级元件
-- 同级元件
执行结果如下:
接口协议:
http: 默认端口号 80,明文传输,没有安全可研, 在开发、测试环境中使用
https 默认端口号 443,加密传输,证书校验,防止中间人中宫攻击,生产环境标配
接口关联:
上一个接口的响应内容,作为下一个接口的请求参数: 通常可以使用变量的方式实现数据传输
1. 从响应中提取数据到变量
1)边界值: 适合简单的数据,每次只能提取一个
2)正则: 适合所有的数据,强大但复杂(用的多)
3)JSONPATH: 适合JSON数据(用的多)
4)XPATH: 适合XML数据
5)CSS: 适合网页数据
2. 从变量中提取数据到请求
${val_name}参数格式:
1. 表单: 发送键值对数据: application/x-www-form-urlencoded
2. JSON: 发送JSON数据(支持多层嵌套,支持多种数据类型)application/json
3. 文件上传: 上传文件 multipart/form-data;key=......通过请求头来声明参数格式
请求URL: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appid&secret=secret
请求方式: GET
参数说明:
参数名 必选 类型 说明 备注 grant_type 是 string 用户类型 填写: client_credential appid 是 string 第三方用户唯一凭证 wxc000c173bbf436ba secret 是 string 第三方用户唯一密钥 d64530af286c88f67eef268bda2642aa 返回示例:
{"access_token": "ACCESS_TOKEN", "expires_in": 7200}返回参数说明:
参数名 类型 说明 access_token string 获取到的鉴权码凭证 expires_in int 凭证有效时间,单位:秒 错误码:
错误码 错误码说明 -1 系统繁忙,此时请开发者稍候再试 40001 AppSecret错误或者AppSecret不属于这个公众号请开发者确认AppSecret的正确性 40002 请确保grant type字段值为client credential 40164 调用接口的IP地址不在白名单中请在接口IP白名单中进行设置。 89503 此IP调用需要管理员确认,请联系管理员 89501 此PP正在等待管理员确认,请联系管理员 89506 24小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用 89507 1小时内该IP被管理员拒绝调用一次1小时内不可再使用该IP调用
使用JMeter测试结果如下: 测试成功
返回结果为:
{"access_token":"82_nzMnHB-l233tJ1iz2xHWxXKuGAP9m9F3KMJ5Q2cKFdfb3xqC7TLpwPwpAoj0xbKXkmHmyZgrq4wpmLD_BNnA1kQ70l-h1pZMfzASw92ZYu01NHNTwgExdI6wsY0AUIeAEAICQ","expires_in":7200}
请求URL: https://api.weixin.qq.com/cgi-bin/tags/get?access_token=access_token
说明: access_token 为上一个用例获取鉴权码的返回值的 access_token
请求方式: GET
返回示例:
{
"tags":[{
"id":1,
"name":"每天一罐可乐星人"
"count":0 //此标签下粉丝数
}
{
"id":2,
"name":"星标组”
"count":0
}
{
"id":127,
"name":"广东”
"count":5
}
]}
4.2.1 直接复制上一个接口的返回值替换到access_token中
使用JMeter测试结果如下: 测试成功
返回结果为:
{"tags":[{"id":2,"name":"星标组","count":0},{"id":1030,"name":"abcd12312312","count":0},{"id":1067,"name":"name","count":0},{"id":1068,"name":"app01","count":0},{"id":1072,"name":"app001","count":0},{"id":1079,"name":"\\u5e7f\\u897f4","count":0},{"id":1089,"name":"wuhan008","count":0},{"id":1090,"name":"wuhan007","count":0},{"id":1092,"name":"wuhan005","count":0},{"id":1093,"name":"wuhan004","count":0},{"id":1101,"name":"\\u6df1\\u5733","count":0},{"id":1102,"name":"\\u65e0\\u7f18","count":0},{"id":1138,"name":"\\u6df1\\u57331","count":0},{"id":1149,"name":"\\u5e7f\\u4e1c","count":0},{"id":1150,"name":"\\u5e7f\\u4e1c2","count":0},{"id":1151,"name":"\\u5e7f\\u4e1c2311141","count":0},{"id":1152,"name":"\\u5e7f\\u4e1c2311132","count":0},{"id":1153,"name":"\\u5e7f\\u4e1c2311192","count":0},{"id":1154,"name":"\\u5e7f\\u4e1c23111580","count":0},{"id":1155,"name":"\\u5e7f\\u4e1c23111260","count":0},{"id":1156,"name":"\\u5e7f\\u4e1c2311150","count":0},{"id":1164,"name":"jiangxi5262","count":0},{"id":1165,"name":"jiangxi61439","count":0},{"id":1166,"name":"jiangxi96958","count":0},{"id":1181,"name":"\\u5e7f\\u4e1c2311144","count":0},{"id":1182,"name":"\\u5e7f\\u4e1c2311145","count":0},{"id":1202,"name":"002","count":0},{"id":1203,"name":"003","count":0},{"id":1204,"name":"\\u5e7f\\u4e1c23111270","count":0},{"id":1205,"name":"004","count":0},{"id":1206,"name":"i001","count":0},{"id":1207,"name":"i002","count":0},{"id":1208,"name":"i003","count":0},{"id":1209,"name":"i004","count":0},{"id":1210,"name":"q001","count":0},{"id":1211,"name":"q002","count":0},{"id":1212,"name":"q003","count":0},{"id":1213,"name":"q0040","count":0},{"id":1214,"name":"s001","count":0},{"id":1215,"name":"s002","count":0},{"id":1216,"name":"s003","count":0},{"id":1217,"name":"s0040","count":0},{"id":1218,"name":"s004","count":0},{"id":1219,"name":"w001","count":0},{"id":1220,"name":"w002","count":0},{"id":1221,"name":"w003","count":0},{"id":1222,"name":"w0040","count":0},{"id":1225,"name":"fNFoirqKsT","count":0},{"id":1226,"name":"Rd8034G5zy","count":0},{"id":1227,"name":"D6rnxQcLTM","count":0},{"id":1228,"name":"jetGZwNxiT","count":0},{"id":1229,"name":"YkA9T8lvxQ","count":0},{"id":1230,"name":"DxYHPA4f0e","count":0},{"id":1231,"name":"xDCpEQsQ2E","count":0},{"id":1232,"name":"ImXYUmUtLv","count":0},{"id":1233,"name":"{{edit_tag_name}}","count":0},{"id":1248,"name":"${time_join_str(\\u56db\\u5ddd)}","count":0}]}
后置处理器的JSON处理器 完成 从响应中提取数据到变量
$ 代表根路径
. 代表字段添加调试取样器,这样就可以看到所有调试的返回值,也就是将第一个接口的响应内容提取到这里
修改请求的路径如下:
重新运行,测试成功
请求URL: https://api.weixin.qq.com/cgi-bin/tags/update?access_token=ACCESS_TOKEN
请求方式: POST
请求参数: {"tag":{"id":134,"name":"王勇"}}
返回示例: {"errcode":0,"errmsg":"ok"}
返回参数说明:
参数名 类型 类型 errcode int 错误码,0表示成功 errmsg string 错误信息,ok表示成功 运行,发现请求参数的 Content-Type 是不正确的,但是结果是对的,其实是微信公众号提供的一些处理功能,实际上不可以的
因为请求头不正确,所以我们添加请求头方式
重新运行,测试成功
使用JMeter测试结果如下: 测试成功
返回结果为:
{"errcode":0,"errmsg":"ok"}
五、JMeter断言
5.1 默认断言
默认情况下,会检查 HTTP 的状态码, 如果状态码 < 400, 视为成功
100: 继续请求
200: 请求成功
300: 重定向(重新发送请求)
400: 客户端错误(请求的不对)
500: 服务端错误(内部 BUG)问题: 现在很多接口会把错误信息放在接口数据中,状态码一律是 200
5.2 断言元件
根据业务需求,自定义测试通过或者失败的标准
1. 如果同时出现 success 和 fail, 断言通过
2. 如果只出现 fail,断言失败sampler_code = prev.getResponseCode() //实际取样代码 sampler_data = prev.getResponseDataAsString() // 实际取样内容 //正则统计关键字出现次数 coun_fail = (sampler_code =~ /fail/ ).size() coun_success = (sampler_data =~ /success/ ).size() log.info("fail: ${coun_fail} success: ${coun_success}") // 判断关键字出现的 if (coun_success==0 && coun_fail >0){ log.info('断言失败') AssertionResult.setFailure(true) prev.setSuccessful(false) //修改取样器结果 AssertionResult.setFailureMessage("有fail出现 ") }else{ log.info('断言成功') AssertionResult.setFailure(false) prev.setSuccessful(true) //修改取样器结果 } log.info("自定义断言完成")
六、基于项目整体分析的实战
6.1 项目接口文档
1.项目使用了Cookies
2.接口返回值有HTML也有JSON
3.失败的标志:fail为失败
4.多出接口关联
5.服务器信息:http://47.107.116.139/
6.需要用到辅助函数:随机数,时间戳,自增ID,UUID
1.项目使用了Cookies
2.接口返回值有HTML也有JSON
3.失败的标志:fail为失败
4.多出接口关联
5.服务器信息:http://47.107.116.139/
6.需要用到辅助函数:随机数,时间戳,自增ID,UUID
1. Cookies管理器
2. 断言: 没有失败
3. 用户变量
4. HTTP默认值
5. 创建公共查看结果树
6. 修改配置D:\software\apache-jmeter-5.6.3\bin\jmeter.properties
CookieManager.save.cookies =true // 将cookie自动保存为变量
断言
1. 无fail(后面自定义断言,被禁用)
2. 相应断言(不存在错误提示断言成功)
3. 自定义断言
http默认请求头:
辅助函数(创建不可重复的值,UUID):
6.4 补全取样器的请求元件
6.4.1 访问phpwind论坛首页接口
七、接口加密解密
7.1 接口加密基础
算法:
不需要还原:哈希算法
速度快,但是无法还原
用于:签名、文件校验常用算法:
MD5
SHA
需要还原:加密算法
速度慢,但是可以还原
对称加密:加密、解密过程对称: AES
非对称加密:加密、解密过程不对称:RSA
7.2 接口加密文档
7.3 接口加密实战
// MD5 加密代码 import org.apache.commons.codec.digest.DigestUtils; username_md5 = DigestUtils.md5Hex("admin"); password_md5= DigestUtils.md5Hex("123"); vars.put("username_mds",username_md5); vars.put("password_mds",password_md5);
// base64 加密代码 import java.util.Base64; encoder = Base64.getEncoder(); username_b64 = encoder.encodeToString("admin".getBytes("UTF-8")); password_b64 = encoder.encodeToString("123".getBytes("UTF-8")); vars.put("username_b64",username_b64); vars.put("password_b64",password_b64);
测试上面的MD5加密的用例, 测试结果符合用例给的参数,测试成功
DDT.csv配置文件内容如下:
username_md5,password_md5,error_code
21232f297a57a5a743894a0e4a801fc3,202cb962ac59075b964b07152d234b70,0
,202cb962ac59075b964b07152d234b70,101
21232f297a57a5a743894a0e4a801fc3,,101
,,101