性能测试工具 Jmeter 之你可能只知其一 (一)

grizz · April 12, 2018 · Last by 甘伟光 replied at May 04, 2018 · 2555 hits

一、 背景:

为了让大家更加的了解 Jmeter,并且使用起来游刃有余,后期会加入性能篇。

1、Debug Sampler 和 Dummy Sampler

首先介绍一下 Jmeter 调试的利器 Debug SamplerDummy Sampler
Debug Sampler会把我们自定义的变量输出在Response Data中,方便我们调试的时候使用,正式执行脚本时需要删除Debug Sampler
Dummy Sampler可以比较方便地模拟测试场景,自定义Request DataResponse Data,在学习测试脚本编写的过程中非常有用。当然,在写文章时又不能把公司的接口暴露了,打码打太多又不舒服,这时候Dummy Sampler使用起来也很舒服。

2、查看结果树& Log Viewer

使用监听器->查看结果树,方便查看接口的详情
打开选项->Log Viewer,方便调试beanshell脚本

二、json 数据的提取

json 数据:

{
    "resultCode": "1000", 
    "resultMsg": "success", 
    "userdic": {
        "Ali": "Moubao"
    }, 
    "userlist": [
        {
            "firstName": "Jiezai", 
            "lastName": "Grizz", 
            "id": 6
        }, 
        {
            "firstName": "Ben", 
            "lastName": "Rose", 
            "id": 8
        }
    ]
}

配置Dummy SamplerRequest DataResponse Data都是上面的 json 串

我们演示一下提取userlist第一个firstName的值Jiezai,提取userlist第二个firstName的值Ben,提取userlist所有firstName的值["Jiezai","Ben"]
演示之前先讲一下我们会用到的JSON Path Extractor(这也是一个 Jmeter 扩展的第三方插件) 的语法
$ 代表整个对象,.代表绝对路径,..代表相对路径,[0] 代表取 list 的第一个值,[0,1] 和 [:2] 都代表取 list 前两个元素

$.userlist
$.userlist[*]
$.userlist[-1:]
$..userlist[?(@.id>7)]
$..firstName
五个表达式的输出依次如下:
》》[{"firstName":"Jiezai","lastName":"Grizz","id":6},{"firstName":"Ben","lastName":"Rose","id":8}]
》》[{"firstName":"Jiezai","lastName":"Grizz","id":6},{"firstName":"Ben","lastName":"Rose","id":8}]
》》[{"firstName":"Ben","lastName":"Rose","id":8}]
》》[{"firstName":"Ben","lastName":"Rose","id":8}]
》》["Jiezai","Ben"]

PS:
1、 正则表达式提取器-》value = "(.+?)"-》模板 $1$
():封装了待返回的匹配字符串。  .:匹配任何单个字符串。
+:一次或多次。       ?:在找到第一个匹配项后停止。
2、 JSON Path Extractor 使用-》可参考:http://goessner.net/articles/JsonPath/
看了之后就知道关于提取其几乎无所不能,因为它像 Xpath 一样有自己的语法。

1、 提取 userlist 第一个 firstName 的值 Jiezai

这个大家应该很在行,用过 Jmeter 提取 json 数据的都知道
使用正则表达式提取器"firstName":"(.+?)"

Debug Sampler中提取结果test3=Jiezai

使用JSON Path Extractor
JsonPath表达式:$..userlist.firstName[0]

Debug Sampler中提取结果test3=Jiezai

2、 提取 userlist 第二个 firstName 的值 Ben

使用正则表达式提取器"firstName":"(.+?)"

或使用正则表达式提取器"firstName":"(.+?)"*"firstName":"(.+?)"

说明一下,模板代表取正则表达式的第几个"(.+?)"
JsonPath表达式:$..userlist.firstName[1],图略。
Debug Sampler中提取结果test4=Ben

3、提取 userlist 所有 firstName 的值 ["Jiezai","Ben"]

只用一次正则表达式好像实现不了,但是使用JSON Path Extractor可以
JsonPath表达式:$..userlist.firstName

Debug Sampler中提取结果test1=["Jiezai","Ben"]

4、比较正则表达式提取器JSON Path Extractor

正则表达式可以提取非 json 数据,JSON Path Extractor只能提取 json 数据 (专业的),但我们一般 HTTP 请求的返回都是 json 格式,因为简洁直观。
当 json 数据返回值为{"firstName": "Jiezai"},正则表达式为"firstName": "(.+?)",如果:(冒号左右边有空格时),其实也是 json 数据,然后正则表达式也需要跟着加,但是不管冒号左右边有没有空格,JSON Path Extractor表达式都为$..firstName

三、时间偏移

1、使用 BeanShell 和 JavaScript

因为公司涉及保险业务,保险期限和犹豫期都会用到时间偏移。
比如我买保险,保险期限一年,保险开始日期是当前日期2018-4-11,则结束日期是当前日期往后推一年2019-4-11。开始日期我们可以用BeanShell表达式
${__BeanShell(${__time(yyyy)})}-${__BeanShell(${__time(MM)},)}-${__BeanShell(${__time(dd,)})}-->输出当前日期2018-4-11
或者JavaScript表达式
${__javaScript((new Date().getFullYear())+'-'+(new Date().getMonth())+'-'+(new Date().getDate()),)}-->输出当前日期2018-4-11
如果往后推一年两个月加三天呢?
BeanShell表达式
${__BeanShell(${__time(yyyy)}+1,)}-${__BeanShell(${__time(MM,)}+2,)}-${__BeanShell(${__time(dd,)}+3,)}
JavaScript表达式
${__javaScript((new Date().getFullYear()+1)+'-'+(new Date().getMonth()+2)+'-'+(new Date().getDate()+3),)}
输出都是2018-5-14
看起来好像是没问题,但是不然,因为当今天是2018-12-30时,上面的表达式结果就是2019-14-33,哈哈哈,表达式不知道一年有多少个月,一个月有多少天,只会简单的往上加。但是如果只是年的偏移上面的表达式还是可以用,如果有月或者天的偏移我们可以借助BeanShell Sampler,其实就是执行 Java 代码。

2、使用 BeanShell 脚本

想执行BeanShell脚本可以使用BeanShell PreProcessor,BeanShell Sampler 或 BeanShell PostProcessor
执行顺序:BeanShell PreProcessor > BeanShell Sampler > BeanShell PostProcessor
BeanShell PreProcessor是前置处理器,在接口调用前执行,我这里就是用的这个
BeanShell Sampler是一个接口请求,会在结果树中体现并写入接口执行报告,只是像执行 Java 代码计算时间偏移不推荐使用。
BeanShell PostProcessor是后置处理器,在接口调用后执行。
举个例子:
只要BeanShell PreProcessorBeanShell PostProcessorBeanShell Sampler的作用域下,就肯定是BeanShell PreProcessor第一个运行,BeanShell Sampler第二个运行,BeanShell PostProcessor第三个运行。

BeanShell PreProcessor的 Java 源码:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
log.info(Label); //输出原件的名称
try{
    log.info("*****时间偏移*****"); //输出日志
    Date date =new Date();  //获取当前时间
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
    String nowDate = sf.format(date);
    Calendar cal = Calendar.getInstance();
    cal.setTime(sf.parse(nowDate));//初始化为当前时间
    cal.add(Calendar.DAY_OF_YEAR,+1);  //后一天
    String orderDate = sf.format(cal.getTime());
    cal.setTime(sf.parse(nowDate));
    cal.add(Calendar.DAY_OF_YEAR,+365);  //后365天,即一年
    String mouthDate = sf.format(cal.getTime());
    vars.put("dayDate",orderDate);//把beanshell的变量传给Jmeter
    vars.put("yearDate",mouthDate);
    log.info("输出计算后的两个时间");
    log.info("dayDate="+orderDate);
    log.info("yearDate="+mouthDate);

}
catch(Exception e){
}

时间偏移的执行结果图:

我们打开 Jmeter 的选项-》勾选Log Viewer,查看 Jmeter 运行日志
执行上面的beanshell源码得到dayDate=2018-04-12yearDate=2019-04-11
beanshell中可以使用log.info()打印调试 beanshell 脚本的日志

四、beanshell 脚本加解密

说到beanshell,不得不说一下我们使用 beanshell 脚本进行密码的加解密操作,因为出于安全的考虑,登录或支付时的密码都会有加解密操作,这里以加密为例:
首先需要开发同学帮忙,把加解的 Java 代码打成password.jar包 (自己命名),然后在 Jmeter 的测试计划页面添加password.jar包,这样才能在 beanshell 中执行 Java 时 import 导入加密的 Java 类。如图:

Jar 包的关键代码,调用FrameDemo.javagenerateKey方法:

比如我们想要把登录密码 qq1111 进行加密,并在 Jmeter 引用的步骤
1、 Jmeter 添加 jar 包
2、 Beanshell 中 import 导入 FrameDemo.java
3、 调用 FrameDemo.java 的 generateKey 方法进行加密操作,generateKey 有 3 个入参,前 2 个是加密的秘钥,第 3 个是待加密的密码,我们秘钥是固定的,待加密的密码是 qq1111,我们可以把待加密的密码写死在 beanshell 中或者从 Jmeter 中传进去。然后我们会选择从 Jmeter 中传进去,这样灵活性更高。
4、 Beanshell 把加密后的字符串传给 Jmeter,供 Jmeter 引用。

BeanShell PreProcessor的 Java 源码:

//在测试计划页面先添加加密jar包
import com.forth.FrameDemo;

//加密
log.info("*****加密*****"); //输出日志
String password = vars.get("login_pw");//输入登录密码-这样写会取Jmeter变量login_pw的值
log.info("jmeter-loginpw="+password); 
String password = "qq1111"; //输入登录密码
String tpk = "30818902818100ccd601e07aeffc7f5f6d20d…";
String kek = "308189028181009fa6334baf70c336163222…";
String loginpw = FrameDemo.generateKey(tpk,kek,password); //调用工具类中的方法进行加密
vars.put("loginpw",loginpw); //把值保存到jmeter变量loginpw中
log.info("loginpw="+loginpw);

PS:
Beanshell 内置对象 vars 对 Jmeter 变量进行存取操作
vars.get("login_pw") //从 Jmeter 中获取变量 login_pw 的值
vars.put("loginpw",loginpw); //把 beanshell 中 loginpw 的值存到 Jmeter 变量 loginpw 中

五、jmeter 属性变量

我们在用 Jmeter 进行接口测试时,每个接口都需要输入IP 和端口,如果我们在 sampler 中把 IP 和端口写死成172.20.128.188 和 8077。那么一旦我们的测试环境迁移,我们就需要对批量的脚本中的批量接口进行修改IP 和端口,修改次数=脚本个数 X 脚本的接口数,那是一件很恐怖的事情,且没有技术含量。聪明的我们知道了,要先在 Jmeter 中把 IP 和端口定义好,那样修改量就会大大减少。如图:

但是这样我们IP 和端口变了,我们还是得一个脚本一个脚本的打开进行修改,修改次数=脚本个数,还是有点麻烦。我们可以试着把 Jmeter 脚本中的 IP 和端口都引用同一个地方的同一个值,其实就是定义一个针对脚本的全局变量,那么 jmeter 属性变量了解一下,如图:

修改次数=1,嗯,完美
Jmeter 的 bin 目录下jmeter.properties文件可以定义 jmeter 属性变量
打开jmeter.properties文件,插入两行

IP=172.20.128.188
PORT=8077

Jmx 脚本中引用:${__P(IP,)}${__P(PORT,)},修改jmeter.properties后,需重启 jmeter 生效。
bin 下user.properties也可定义 jmeter 属性值,那么问题来了,user.propertiesjmeter.properties同时定义 PORT 的值,一个是 8077,一个是 8078,最终${__P(PORT,)}引用时值会是多少?好奇的朋友可以试一下然后留言答案,顺便探讨 Jmeter 的其它知识。Git还没弄好,弄好后把代码和相关脚本一起放过去。

感觉自己的排版和描述可以优化,特别是代码和英文的排版,希望有大佬看到可以提点一下,感谢阅读。

下一篇:Jmeter 接口自动化 - 脚本数据分离实例 (应该有很实用的骚操作)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 6 条回复 时间 点赞
grizz 关闭了讨论 12 Apr 11:58
grizz 重新开启了讨论 12 Apr 11:58

二、json 数据的提取 中用正则表达式以及 json path 提取器 提取后用什么查看提取内容是否正确呢?

grizz #4 · April 12, 2018 Author
MBF 回复

两种方法,以 json path 表达式 $..userlist.firstName,提取后变量值为 test1 为例:
1、引用,调试的时候可以在 sampler 的名称中 ${test1}引用,然后去查看结果树看 sampler 的名称
或者在接口的请求中调用也可以,原理是一样的。

2、添加 Debug Sampler,然后去查看结果树看 Debug Sampler 的响应数据

grizz #5 · April 12, 2018 Author

Jmeter 的扩展插件,JMeterPlugins-Extras.jarJMeterPlugins-Standard.jar(放到 jmeter/lib/ext 目录下即可):

可自行下载,如果需要的也可以联系我,文件目前上传不了,所以没放

grizz 回复



尝试了一下没取到😂

grizz #7 · April 12, 2018 Author
MBF 回复

你设置 Dummy Sampler 的 Response Data 为这个试试,不知道你是不是复制之后的格式有问题:
{"resultCode":"1000","resultMsg":"success","userdic":{"Ali":"Moubao"},"userlist":[{"firstName":"Jiezai","lastName":"Grizz","id":6},{"firstName":"Ben","lastName":"Rose","id":8}]}

还真去试了下,两个都改了,使用的是 user.properties 里的 port😀

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up