个人学习笔记(整理不易,有帮助点个赞)
笔记目录:学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客
目录
一:了解常用组件
二:创建测试计划
1)先新建一个测试计划
2)创建线程组
二:配置元件
1)HTTP请求默认值
2)HTTP消息头管理器
3)参数化配置元件(csv data set config)
4)创建用户定义的变量
三:监听器:如聚合报告,查看结果树
1)聚合报告
2)查看结果树
四:逻辑控制器
1)if控制器
2)仅一次控制器
3)吞吐量控制器
五:取样器
1)HTTP请求
六:定时器
1)固定吞吐量定时器
2)固定定时器
七:前置处理器
八:后置处理器
1)正则表达式提取器
2)BeanShell后置处理器
九:断言
1)响应断言
2)Size断言
3)BeanShell断言
一个jmx测试脚本对应一个测试计划;一个测试计划至少包含一个线程组;
多个线程组之间可并行运行,也可串行运行
任何类型的测试都要先创建线程组(任何内容都放在线程组中),一个线程组可看作一个虚拟用户组,其中的每个线程模拟为一个虚拟用户
如:测试数据库,用于执行打开数据库连接操作
如:测试数据库,用于执行关闭数据库连接操作
在取样器错误后要执行的动作:就是在错误之后要如何执行
假设线程数是50,启动时长是2s,则表示2s启动50个线程(50/2),那么平均每秒启动25个线程
假设这个线程组有3个HTTP请求,循环是3,则表示一个线程会发送9个HTTP请求;如果选择永久(Infinite),所有线程无限循环发送请求;如果选择了永久且调度器设置中设置了持续时间,则会在持续时间到达后结束循环
此项一般与Ramp-up Period (in Seconds)一起使用
假设线程数为10,Ramp-up Period (in Seconds)是100,则不选择此项,测试会每隔10秒启动1个线程,那么100秒后会有1~10个线程运行
(为什么会出现1~9的情况?那是因为线程执行完成时间可能低于100秒,这样就导致设置的10个并发场景并未完全生效,当然线程执行时间大于100秒的话可不选)
如果选择此项,那么线程组会每隔10秒创建1个线程,但是不执行,等100秒后,10个线程全部创建完成,再同时执行
持续时间:表示线程组测试的持续时间。如果选择了永久(Infinite)且调度器设置中设置了持续时间,则会在持续时间到达后结束循环。这个时间不要小于Ramp-up Period
启动延迟:表示启动测试后多久开始创建线程组,常用于定时
ps:
1:线程组不使用可以禁用掉
2:时间和线程数的要求,一方面来自甲方要求;另一方面根据业务来定,假设一天有10000用户,最高峰期(性能测试一般看这个),10分钟内有1000用户,则1分钟100个用户,1秒大概1个用户,那在测试的时候可以提高这个值,比如1秒10用户
3:如果要测试阶段式压力的情况,即从某个值开始不断增加压力,直至到达某个指定值,然后持续运行一段时间,这个时候需要安装插件
下载地址:Install :: JMeter-Plugins.org
下载jmeter-plugins-manager-1.8.jar,丢到lib\ext下,重启即可
详情使用可参考:Jmeter-plugins-manager插件的安装与使用_晒不黑的黑煤球的博客-CSDN博客
http:使用80端口;https:使用443端口
一般用作全局的配置,后面同样IP地址和端口的请求就不用重复填了
如前端要给后端验证cookie/token之类的信息,就是将其添加到消息头里的
Header |
解释 |
示例 |
---|---|---|
Accept |
客户端能接受的响应内容类型 |
Accept: text/plain, text/html |
Accept-Charset |
浏览器可以接受的字符编码集 |
Accept-Charset: utf-8 |
Accept-Encoding |
可接受的响应内容的编码方式 |
Accept-Encoding: compress, gzip |
Accept-Language |
可接受的响应内容的语言列表 |
Accept-Language: en,zh |
Accept-Ranges |
可以请求网页实体的一个或者多个子范围字段 |
Accept-Ranges: bytes |
Authorization |
HTTP授权的授权证书 |
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control |
指定请求和响应遵循的缓存机制 |
Cache-Control: no-cache |
Connection |
客户端/浏览器优先使用的连接类型(HTTP 1.1默认进行持久连接) |
Connection: close Connection:keep-alive |
Cookie |
HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 |
Cookie: $Version=1; Skin=new; |
Content-Length |
以八进制表示的请求体的长度 |
Content-Length: 348 |
Content-Type |
请求体的MIME类型 |
Content-Type: application/x-www-form-urlencoded |
Date |
请求发送的日期和时间 |
Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect |
客户端要求服务器做出的特定行为 |
Expect: 100-continue |
From |
发出请求的用户的Email |
From: [email protected] |
Host |
服务器的域名和端口号,如果端口号是80可省略 |
Host: www.zcmhi.com |
If-Match |
仅当客户端提供的实体与服务器对应的实体相匹配时,才能进行对应的操作。 主要用于像PUT这样的方法中,仅当从用户上次更新某个资源后,该资源未被修改的情况下,才更新该资源 |
If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since |
如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 |
If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match |
如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 |
If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range |
如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag |
If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since |
只在实体在指定时间之后未被修改才请求成功 |
If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards |
限制信息通过代理和网关转发的次数 |
Max-Forwards: 10 |
Pragma |
用来包含实现特定的指令 |
Pragma: no-cache |
Proxy-Authorization |
连接到代理的授权证书 |
Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range |
只请求实体的一部分,指定范围 |
Range: bytes=500-999 |
Referer |
先前网页的地址,当前请求网页紧随其后,即来路 |
Referer: http://www.zcmhi.com/archives/71.html |
TE |
客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 |
TE: trailers,deflate;q=0.5 |
Upgrade |
要求服务器将协议升级到一个高版本(如果支持) |
Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent |
浏览器的身份标识字符串 |
User-Agent: Mozilla/5.0 (Linux; X11) |
Via |
通知中间网关或代理服务器地址,通信协议 |
Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning |
关于消息实体的警告信息 |
Warn: 199 Miscellaneous warning |
注意:
1)Jmeter支持添加多个消息头管理器,会合并成一个消息头跟随请求提交,而且不同管理器内有名称相同的消息头条目存在时,顺序靠前的管理器的消息头条目会覆盖后面的
2)Jmeter中有cookie manager和cache manager,如果要模拟客户端无缓存场景,需要组件选择“每次反复清除cookies”和“clean cache each iteration”
参数:
文件名(Filename) : 包含参数文件路径
1、这里要包括文件的路径,在4.0版本中可以点击右侧的浏览按钮选择文件,会自动带上文件的绝对路径
2、当csv文件在JMeter的bin目录或脚本目录时,只需文件名
3、使用相对路径时,JMeter默认先去bin目录下查找,然后去脚本目录下查找
文件编码:如utf-8
变量名称:csv文件中各列的参数名称,多个参数用逗号隔开
Ignore first line:忽略首行(只在变量名称不为空时才生效)
如果文件中第一行为参数名称,选择true
如果文件中第一行为参数值,选择false
此项只在设置了变量名称后才生效
Delimiter:分隔符,“\t”表示tab键
Allow quoted data?:是否允许带引号
当为False时,csv文件中有引号,但变量实际引用后会变成自动去掉引号
当为True时,csv文件中有引号,则变量引用后也带引号
Recycle on EOF?:遇到文件结束符是否再次循环
True—继续从文件第一行开始读取,False—不再循环;
此项与Stop thread on EOF为互斥关系,若Recycle on EOF设置为True,则Stop thread on EOF需要设置为False
Stop thread on EOF?:遇到文件结束符是否停止线程
True—停止,False—不停止;
注意:当Recycle on EOF设置为True时,此项设置无效
Sharing mode:线程共享模式
所有线程(All threads):测试计划中所有线程共享csv文件中的记录(所有线程按照顺序取文件中的记录)
当前线程组(Current thread group)::线程组中的所有线程共享csv文件中的记录(每个线程依次读取csv数据)
当前线程(Current thread):每个线程各自独立使用csv的记录
比如要用到的全局变量
Label:每个JMeter的element的Name值,例如HTTP Request的Name
样本:发出请求数量;模拟10个用户,循环10次,所以显示了100
平均值(重点):平均响应时间(单位:ms);总运行时间/发送到服务器的请求数
默认是单个Request的平均响应时间,当使用了事务控制器(Transaction Controller)时,也可以以Transaction为单位显示平均响应时间
中位数:50%的用户响应时间小于这个值
95%百分位(重点):95%的用户响应时间小于这个值
99%百分位:99%的用户响应时间小于这个值
最小值:用户响应时间最小值
最大值:用户响应时间最大值
异常%:测试出现的错误请求数量百分比;请求的错误率 = 错误请求的数量/请求的总数;若出现错误就要看服务端的日志查找定位原因
吞吐量:Throughput简称TPS,吞吐量,默认情况下表示每秒处理的请求数,也就是指服务器处理能力,TPS越高说明服务器处理能力越好
KB/sec:每秒从服务器端接收到的数据量
用来调试脚本的时候观察请求和响应是否正确
ps:真正压测的时候禁用或者删除该组件,否则会影响性能
用于控制脚本的执行顺序,目前有2类共17种逻辑控制器
一类是控制测试计划中节点发送请求的逻辑顺序控制器,如if,switch,Loop,Random控制器等
一类是用于组织和控制节点的,如事务,吞吐量控制器等
默认情况下,条件在初始输入时仅判断一次,但可以选择对控制器中包含的每个可执行请求进行判断
使用建议选择“Interpret C a V E”选项,即将条件解释为变量表达式。通常如果有无法使用变量表达式解释器执行的复杂条件,最好使用Groovy和JEXL解释器
在循环执行中执行一次仅一次控制器下的请求,然后在接下来的循环执行中将会跳过该控制器下的所有请求
通常用于登录的测试中,仅一次控制器相当于LR中的init初始化事务
用于控制其下的子节点的执行次数与负载比例分配
1)线程2,循环次数2(样本4)----------------不使用吞吐量控制器的情况下
2)线程2,循环次数2;percent execution:25,不勾选Per user(样本1)
注意:如果还是上述条件,但勾选了per user,则不能生成数据(这样使用的实际意义没搞懂)
如果勾选了per user,但是不填写吞吐量,则样本数量是:线程数*循环次数=2*2=4
3)线程2,循环次数2;total executions:3,不勾选Per user(样本3)
注:吞吐量填写次数超过线程样本数,只会执行到最大值
注意:还是上述条件,勾选Per user,结果样本4(这样使用的实际意义没搞懂)
如果勾选了Per user,但是不填写吞吐量,则样本数量是2
Per user勾选后对所有线程数都有效,不勾选仅对单个线程数生效,而且勾选Per user后,当选择T E时,表示线程数;当选择P E时,表示线程数*循环次数
取样器是用来模拟用户操作的,它可以向服务器发送请求以及接收服务器的响应数据
最常见的是HTTP请求和Java请求
常见的有GET和POST
添加HTTP请求
参数说明:
ps:
如果协议、IP、端口不填则使用HTTP请求默认值,如果填了则使用填好的请求
自动重定向只会跳到最终的页面(一般使用这个),跟随重定向会把中间每一个过程都记录下来
鉴权码一般通过接口获取,或者通过登录生成获取
常见重定向:
1)301:永久性重定向,表示请求的资源已经永久性分配了新的URI,以后使用新的URI。使用Location首部字段表示新的URI地址,浏览器会重新请求一次该URI
2)302:临时性重定向,表示希望用户本次使用新分配的URI
默认情况下,Jmeter线程在发送请求之间没有间歇,为了真实模拟用户请求情况,定时器用于在用户操作之间设置等待时间
定时器在每个取样器之前执行,无论定时器位置在之前还是之后
执行一个取样器之前,所有当前作用范围内的定时器都会执行,如果要对其中一个取样器生效,需要将定时器作为子节点加入
用来设置QPS限制,控制给定的取样器发送请求的吞吐量,常用于混合压测过程中同时压测多个接口,这种场景下对每个接口的吞吐量设置一个上限
target Throughput :目标吞吐量
ps:
该定时器只有在线程组中的线程产生足够多请求的情况下才有意义。因此,即时设置了固定吞吐量定时器的值,也可能由于线程组中的线程数量不够,或者定时器设置不合理等原因,导致总体的QPS不能达到预期目标
如果要将定时器应用于单个定时器,则要将定时器添加为取样器的子元素。在执行取样器之前将应用定时器。如果要在取样器之后应用定时器,则要将定时器添加到下一个取样器
使用该项,可以让每个线程在两次请求之间暂停相同的时间
用于请求发送之前对请求进行处理,如参数化,加密请求,替换请求等
ctx:访问Jmeter运行时状态,如线程数和线程状态
vars:访问定义的变量
props:访问运行时设置
prev:访问前一个取样器结果
sampler:访问当前取样器
log:写日志
如接口性能测试,需要每次api请求的入参都是变化的
比如有些接口请求参数有时间戳
比如要保证接口请求的安全性,需要请求携带sign入参,且sign是通过加密算法得到
也就是说,这样的接口基本每次请求前需要构建不同的入参数据。因此需要参数化请求入参,BeanShell Preprocessor(BeanShell预处理程序)这样的前置处理器就可以很方便构建参数化入参。
实例:参数化时间戳、参数化sign签名
// 导入MD5加密需要用到的jar包。
import org.apache.commons.codec.digest.DigestUtils;
// 声明你需要拼接的字符串
//String requestTime = "1680089472000"; //生成时间戳
String requestTime = "${__time(,)}"; //生成时间戳
log.info("=====请求时间戳:=====>>"+requestTime);
String yan = "N[8HXx!57Ivy%)#R";
//拼接需要加密的字符串
String str = yan + requestTime;
// 加密已拼接的字符串
String sign = DigestUtils.md5Hex(str);
log.info("=====sign签名:=====>>"+sign);
vars.put("sign",sign); //设置变量,将md5加密后的值传递给变量sign
vars.put("requestTime", requestTime); //将时间传递给变量requestTime
如BeanShell脚本中,定义了两个字符串:requestTime 和sign,设置了它们的取值
其中requestTime的值是${__time(,)},该函数可以按当前时间生成时间戳
sign的值是进行了md5加密后生成了一个32位小写的字符串
并将值传给变量requestTime 和sign用于后续接口请求参数化
将脚本中的requestTime 和sign参数引入接口请求中
用于请求发送之后得到服务器响应进行处理
接口关联
简单的说就是用前一个请求的数据作为后一个请求的参数的值,比如:
引用名称:其他地方引用提取值的变量名称
模板:表示使用提取到的第几个值。如果前面的正则表达式提取了不止一个参数(多个括号括起来),那么这里需要指定参数的组别,格式为$n$,比如$1$,$2$等,表示解析到的第几个值给引用名称变量。正则表达式的提取模式,值是从1开始,值为0则对应的是整个匹配的表达式
匹配数字:0代表随机取值,-1是全部取值,其余正整数代表在已提取的内容中,匹配第几个内容
默认值:如果正则表达式没有查到值,则使用默认值
方法一:使用正则表达式实现接口关联(可以作用于任意值)
新建一个请求:http://www.weather.com.cn/data/sk/101010100.html
运行能看到结果
如果出现乱码,可以添加一个BeanShell后置处理程序,加上代码:prev.setDataEncoding("utf-8");
在查看结果树这里可以查看结果,也可以进行一些测试,比如正则表达式的测试:
其中:
()
:封装了待返回的字符串.
:匹配任意一个字符(除了回车键)*
:限定符,匹配(*之前的符号)0次或多次,默认是贪婪模式+
:限定符,匹配(+之前的符号)1次或多次,默认是贪婪模式?
:限定符,匹配0次或1次,在找到第一个匹配项后停止.*:
匹配连续0个/多个字符.+:
匹配连续1个/多个字符\
:转义,\.
表示匹配字符.本身
^:边界限定,字符串的开始位置
$:边界限定,字符串的结束位置
| :模式限定符,从中任选一个匹配
PS: 这个怎么理解,.是提取一个,但是使用了*或者+,那就会一直提取到最后一个字符串,?表示第一个匹配到第一个项就停止(也就是北京,然后有“,就停止了),所以如果不加?,则一直匹配到B为止
添加一个后置处理器:正则表达式提取器
提取器作用的范围:
提取器取值的范围:
其中:
$1$表示解析到的第1个值,$$是固定写法
匹配数字:0代表随机取值,1表示匹配返回数组的第一个元素内容
缺省值:如果参数没有取得到值,那默认给一个值让它取
如果是取2个值就这样
增加一个调试取样器,用于查看结果有没有取值到
这里我遇到一个问题,我把后置处理器放在正则提取器的后面,得到的结果是???,放在提取器前面就好了,我理解是请求后的数据,要先处理乱码,再提取,才是正确的
方法二:使用Jsonpath表达式实现接口关联(只能作用于返回值是Json的)
1)从根目录开始找(绝对路径):$.weatherinfo.temp
2)从任意目录开始找(相对路径):$..city
ps:如果需要宣线程组之间引用变量值,那么在正则表达式提取后,还要添加一个后置处理器
通过设置属性的方式,${_setProperty(globalToken,${token},)}
另一个线程组中可以引用属性值globalToken
如果数据量小,建议使用正则表达式。如果数据量大或者对取值有特殊要求,可以考虑使用BeanShell后置处理器
针对数据比较大且返回有多个列表,需要人工判断需要取的值是否存在并且在什么位置,导致效率低而且容易出错,则可以使用BeanShell后置处理器,分别提取每个列表的值
可以将前一个接口的响应结果的某个字段用于下一个接口的参数
可以获得登录接口的 token ,并保存到本地
FileWriter fs= new FileWriter("E:\\token.csv",true);
BufferedWriter out = new BufferedWriter(fs);
//phone 是 login 接口的入参,这里一并写入文件
out.write(vars.get("phone")+',');
out.write(vars.get("token")+'\n');
out.close();
fs.close();
一些使用代码也可以参考:武汉茑萝:Jmeter之BeanShell详解 - 知乎
用于自动化验证取样器请求,或对应的响应数据是否返回了期望的结果
响应断言提供对取样器的响应文本、响应代码、响应信息、响应头、请求头、URL样本、文档、请求数据等内容进行包括,匹配,相等,否,或等判断,可以将多个断言附加到任何控制器以提高灵活性
(1)Apply to:响应断言的应用范围
(2)要测试的响应字段
(3)模式匹配
(4)测试模式
要测试的模式。在这里输入结果期望值,注意空格要去掉,可以设置多个期望值,如果其中一个执行失败,则不再继续检查
(5)自定义失败消息
断言失败时,打印的消息
ps:如果返回报文是这种情况
{resultcode:200,message:XXX},如果配置检查点resultcode:200,可能会存在漏判,比如
{resultcode:200,message:null},这样检查点虽然成功,但事务是失败的(如后端调用超时或者失败,通信层面是成功的,但消息没成功返回),因此需要额外的关键字作为检查点
用来判断返回内容字节大小,以及响应结果是否包含正确数量的字节
比如大报文返回场景:
{resultcode:200,list:[..................................123...............................................],}
如果断言为123做内容检查就会出现问题。假设list返回10条,实际上只返回1条,虽然检查通过了,但实际事务失败。所以需要Size断言做额外判断
如果断言不能直接进行判断,需要进行一定的转换处理,就需要BeanShell断言。
它可以通过BeanShell脚本来执行断言检查
假如有以下响应数据
{
2 "message": "XXX",
3 "Code": 200
4 }
(1)使用JSONObject对象来获取json数据,要下载org.json的jar包,在测试计划中导入该jar包,在jmeter的lib目录下放入该jar包
验证Code的值是否等于200,验证message的值是否符合预期
// 获取上一个请求的返回值
String jsonString = prev.getResponseDataAsString();
// 将【返回值】转换为JSON格式
JSONObject responseJson = new JSONObject(jsonString);
if (responseJson.getInt("Code") != 200){
Failure = true;
FailureMessage = "statusCode的返回值错误";
}
String message1 = responseJson.getString("message");
log.info(message1);
if (!message1.equals("XXX")){
Failure = true;
FailureMessage = "message与实际值不一致";
}
(2)如果有以下数据,要解析数组中data的值
{
"Code": 200,
"data": [
{
"i": "1234",
"n": "XXX",
"v": "2.0",
"iconUrl": "",
},
{
"i": "4567",
"n": "YYY",
"v": "3.0",
"iconUrl": "",
}
]
String jsonContent = prev.getResponseDataAsString();
JSONObject response = new JSONObject(jsonContent);
JSONArray groups = response.getJSONArray("data");
String strData= groups.toString();
log.info(strData)
(3)如果有更复杂的结构,要解析groups的数据
{
2 "priorityGroups": {
3 "proId": 1234,
5 "groups": [
6 {
7 "id": "345677",
8 "items": [
9 {
10 "proId": 1111,
11 "n": "PC端",
12 "index": 1
13 },
14 {
15 "proId": 2222,
16 "n": "iOS端",
17 "index": 2
18 }
24 ]
25 }
26 ]
27 },
28 "promotion": {
29 "proId": 1234,
38 "createTime": 1111111111111
39 }
40 }
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
String jsonContent = prev.getResponseDataAsString();
JSONObject response = new JSONObject(jsonContent);
JSONArray groups = response.getJSONObject("priorityGroups").getJSONArray("groups");
String strGroups = groups.toString();
(4)响应头解析
// 4. 响应头解析
import java.util.HashMap;
import java.util.Map;
//将字符串用换行符 截取为abc数组
String [] headersList = ResponseHeaders.split("\n");
Map headersMap = new HashMap(); // 创建HashMap来重新组装headers
for (int i=1; i