JMeter日常总结

断言

在对接口进行测试时,通常需要对接口调用结果进行断言,以确定接口调用是否达到预期,同时也可以在结果数中看到接口是否调用成功。响应断言jp@gc - JSON Path Assertion是比较简单和常用的两个断言器。

在HTTP请求下添加断言 -> 响应断言,可以通过不同的模式匹配规则进行匹配断言。

在HTTP请求下添加断言 -> jp@gc - JSON Path Assertion。目前看来该断言器只能断言其中一个字段。

一般来说以上两种断言器已经基本够用了,如果遇到比较复杂的可以使用BeanShell断言来通过脚本进行断言。

变量提取使用

通常在测试时接口需要进行鉴权,这是通过调用登录接口获取到token_id然后在调用具体接口时将token_id作为参数或者放在header中传入。这里就需要将token_id从鉴权接口的响应中提取出来,然后再使用时传入。

对于鉴权接口在JMeter中可以通过在测试计划中添加setUp Thread Group,并将线程数循环次数设置成1,并在该线程组中添加鉴权接口的HTTP请求。可以添加常规的断言察看结果树。也可以在线程组中添加逻辑控制器 -> 仅一次控制器将鉴权接口相关类容添加至该逻辑控制器下。

目前我用到的变量提取有JSON Extractor正则表达式提取器两种。当然还有其他的提取器,目前这两种提取器基本够用了。

JSON Extractor其实是通过XPath从JSON串中取出目标值。

正则表达式提取器当然是通过正则表达式的方式从字符串中提取目标值。

虽然将变量从响应结果中提取出来了,但是并不能直接使用。可以通过BeanShell PostProcessor将参数设置为全局变量,也可以将其存储到本地文件中使用时通过CSV Data Set Config来读取并使用。

设置成全局变量相对简单,只需要在BeanShell PostProcessor中配置${__setProperty(token_id, ${token_id},)}脚本即可。这里的print会将提取到的变量打印到cmd窗口中。在使用变量时通过${__property(token_id)}进行获取。

将变量存储到本地文件中,也是通过BeanShell PostProcessor脚本实现的,只是相对于设置全局变量复杂得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.regex.Matcher; 
import java.util.regex.Pattern;
//JMeter的内置API:prev.getResponseData()获取请求的响应内容
byte[] responseData = prev.getResponseData();
//定义正则表达式需要匹配的模式提取相关变量
Pattern pattern = Pattern.compile("\"token_id\":\"(.+?)\"");
Matcher result = pattern.matcher(new String(responseData)); //boolean java.util.regex.Matcher.find()只要找到符合条件的就返回true
if(result.find()){
String tokenId += result.group(1)+"\r\n";
//导出的csv存放位置
String filePath = "D:/test/token.txt";
BufferedOutputStream bos = null;
FileOutputStream fos = null;
try {
File file = new File(filePath);
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(tokenId.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
}
}

使用时通过CSV Data Set Config来读取到配置中。

JMeter压测的坑

在JMeter中通过线程组的方式进行并发压测,但是实际测试中发现,JMeter其实实际上是一个同步的方式去发送请求的,当我们同时压测几个接口时,通过聚合报告很明显的看出JMeter会等到前一个接口结束后才会请求下一个接口。

在单个接口做并发测试时,当我们的并发设置为150时,JMeter的并发请求数确实是150,但是JMeter会等到其中某个请求结束然后再补充一个请求,通俗的将若你的接口延时1分钟,JMeter在这1分钟内只会发150个请求,当其中有请求结束再往里面补充一致维持150个请求。并不能完全模拟真实场景下的高并发。

通过MBean监控Tomcat的collectionCount参数也可以明显的看出这一点:

150-async-false-mbean

JMeter BindExecption:Address already in use:connect

在Windows10环境下,通过JMeter对接口进行压测时,在100的并发下聚合报告中会出现百分之三点几的错误率,在150的并发下出现了百分之三十几的错误率,当然在不同的环境和接口响应速率下这个错误率可能会不一样。

150-async-false-jmeter-error

具体原因是由于端口被占用,Windows提供给TCP/IP连接的端口为1024-5000,并且要四分钟来循环回收他们。就导致我们在短时间内跑大量的请求时将端口占满了。

解决方案:

  1. cmd中,用regedit打开注册表
  2. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
    1. 右击parameters,添加一个新的DWORD,名字为MaxUserPort
    2. 然后双击MaxUserPort,输入数值数据为65534,基数选择十进制。
  3. 然后重启电脑!重启电脑!重启电脑!

Gzip压缩请求

对于Gzip压缩请求,通常做法是添加JSR223 PreProcessor预处理程序,将请求内容进行压缩。

Gzip压缩预处理

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.commons.io.IOUtils;
import java.util.zip.GZIPOutputStream;


String bodyString = sampler.getArguments().getArgument(0).getValue();
byte [] requestBody = bodyString.getBytes("utf-8");
ByteArrayOutputStream out = new ByteArrayOutputStream(requestBody.length);
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(requestBody);
gzip.close();

sampler.getArguments().getArgument(0).setValue(out.toString(0));

在上述代码中的getBytes("utf-8")最好加上utf-8的编码格式,否正日志可能乱码。

值得注意的是,在HTTP Request中的Content encoding中的编码方式一定不要填,否正很有可能导致乱码,从而导致请求失败。