JMeter 脚本

🍕 通过使用脚本来执行高级操作,例如:创建复杂的测试场景、自定义请求参数、处理响应数据、编写逻辑控制和循环等。

1 获取验证码

  • 获取验证码
    • (组件)HTTP请求:获取验证码图片路径并进行请求。
    • (组件)保存响应到文件:保存验证码图片到本地电脑。
    • (组件)OS进程取样器:加载Python脚本,再进行识别。
    • (组件)正则表达式提取器:提取脚本识别到的验证码。
    • 验证码一般用在需要验证用户身份或防止机器人自动化操作的地方。
    • 常见的应用场景包括:用户注册、登录、密码重置、支付验证等等。
    • 日常测试时,一般不提倡获取验证码测试(目的不在验证用户身份)。

1-1 Python脚本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# vcode.py,该脚本只对较为清晰的数字验证码有效
import ddddocr

ocr = ddddocr.DdddOcr()

with open("D:\\Program\\JMeter\\验证码.jpeg", "rb") as f:
img_bytes = f.read()

# ocr需要使用到Pillow库10.0.0之前的版本
# 若报错AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
# 则卸载:pip uninstall pillow,再重装:pip install pillow==9.5.0
res = ocr.classification(img_bytes)

print("{t:", res.replace(" ", ""), "}")

1-2 保存响应到文件

  • 保存响应到文件
    • 验证码图片保存到指定位置,在“文件名称前缀”右侧输入保存路径。
    • 路径包括图片名称,不需要图片后缀,JMeter默认以.jpeg格式保存。
    • 勾选Don't add number to prefix选项,不给图片名后缀加数字编号。

获取验证码-保存响应到文件

1-3 OS进程加载脚本

  • OS进程加载脚本
    • 命令指定为python,工作目录设置成Python脚本的路径。
    • 命令行参数值指定Python脚本的文件名称,如vcode.py

获取验证码-OS进程加载脚本

1-4 正则表达式提取器

  • 正则表达式提取器
    • 引用名称:自定义,该值即验证码的参数,后续引用使用。
    • 正则表达式
      • \{t:\s*(\d+)\s*\}:匹配以{t:开头}结尾的字符串。
      • \s*t:后面跟着0个或多个空格,\d+至少一个数字。
    • 模板为$1$(解析出的第一个值),匹配数字为1(第一个值)。

获取验证码-正则表达式提取器

2 BeanShell

  • BeanShell
    • log.info()(写日志到控制台)、ctx.getProperties()(获取上下文所有的变量)。
    • prev.getResponseDataAsString()(获取响应数据)、prev.getResponseCode()(获取响应码)。
    • vars.get(variable_name)(获取变量的值)、vars.put(variable_name, value)(可将值保存到变量中)。
    • props.get(property_name)(获取JMeter属性)、props.put(property_name, value)(只保存String值)。

2-1 打印信息

  • 打印信息
    • 线程组下添加一个BeanShell取样器,将代码放入脚本中。
    • 菜单栏:选项->日志级别->勾选TRACE,或者DEBUG。
    • 启动线程,点击右上角的⚠图标,在底部的日志框中查看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 打印变量
log.info("打印变量---------------------");

// 打印日志
log.info("信息日志---------------------");
log.warn("警告日志---------------------");
log.error("错误日志---------------------");
log.debug("调试日志---------------------");

// 获取JMeter属性值
log.info("获取到的JMeter属性值:" + props.get("jmeter.version"));

// 修改JMeter属性值
props.put("jmeter.version", "5.7");

// 获取修改后的JMeter属性值
String newVersion = props.get("jmeter.version");
log.info("修改后的JMeter属性值:" + newVersion);

2-2 获取时间

  • 获取时间
    • 线程组下添加一个BeanShell取样器,将代码放入脚本中。
    • 再添加一个调试取样器和察看结果树,启动线程查看结果。
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
30
31
32
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

// 获取当前时间
LocalDateTime now = LocalDateTime.now();

// 将时间格式化为不同的形式
String formattedDateTime = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String year = now.format(DateTimeFormatter.ofPattern("yyyy"));
String month = now.format(DateTimeFormatter.ofPattern("MM"));
String day = now.format(DateTimeFormatter.ofPattern("dd"));
String hour = now.format(DateTimeFormatter.ofPattern("HH"));
String minute = now.format(DateTimeFormatter.ofPattern("mm"));
String second = now.format(DateTimeFormatter.ofPattern("ss"));

// 将时间存储到JMeter变量中
vars.put("当前时间", formattedDateTime);
vars.put("年份", year);
vars.put("月份", month);
vars.put("日期", day);
vars.put("小时", hour);
vars.put("分钟", minute);
vars.put("秒钟", second);

// 打印信息到日志
log.info("当前时间:" + formattedDateTime);
log.info("年份:" + year);
log.info("月份:" + month);
log.info("日期:" + day);
log.info("小时:" + hour);
log.info("分钟:" + minute);
log.info("秒钟:" + second);

2-3 结果断言

  • 结果断言
    • 线程组下添加一个HTTP请求,用于请求接口。
    • 确保该接口可正常返回响应数据,并且包含“success”字符串内容。
    • HTTP请求下添加一个BeanShell断言组件,将断言代码放入脚本中。
    • 最后添加一个察看结果树,启动线程后,在察看结果树中查看结果。
1
2
3
4
5
6
7
8
9
10
String response = prev.getResponseDataAsString();

// 验证响应中是否包含"success"字符串
if (!response.contains("success")) {
Failure = true;
FailureMessage = "响应中未包含'Success'字符串!";
} else {
Failure = false;
FailureMessage = "响应中包含了'Success'字符串!";
}

2-4 Base64加密

  • Base64加密
    • 线程组下添加一个用户定义的变量,用于存储username和password。
    • 再添加一个BeanShell预处理程序,将BeanShell代码复制到脚本中。
    • 最后添加一个调试取样器、一个察看结果树,启动线程后,查看结果。
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 org.apache.commons.codec.binary.Base64;

// 得到username变量的值
String username_var = vars.get("username");
String password_var = vars.get("password");

// 查看log日志,判断username的值是否正确
log.info("Username: " + username_var);
log.info("Password: " + password_var);

if (username_var == null || password_var == null) {
log.error("Username or Password is null!");
throw new IllegalArgumentException("Username or Password cannot be null.");
}

try {
// 加密
String username_str = Base64.encodeBase64String(username_var.getBytes("utf-8"));
String password_str = Base64.encodeBase64String(password_var.getBytes("utf-8"));
log.info("Encoded Username: " + username_str);
log.info("Encoded Password: " + password_str);

// 加密后把值赋给username_enc变量
vars.put("username_enc", username_str);
vars.put("password_enc", password_str);
} catch (UnsupportedEncodingException e) {
log.error("Character Encoding not supported.", e);
throw new RuntimeException(e);
}

2-5 Base64解密

  • Base64解密
    • 线程组下添加一个用户定义的变量,用于存储username和password。
    • 再添加一个BeanShell取样器,将BeanShell代码复制粘贴到脚本中。
    • 最后添加一个调试取样器、一个察看结果树,启动线程后,查看结果。
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 org.apache.commons.codec.binary.Base64;

// 得到加密后的username变量的值
String username_var = vars.get("username");
String password_var = vars.get("password");

// 查看log日志,判断username的值是否正确
log.info("Username: " + username_var);
log.info("Password: " + password_var);

if (username_var == null || password_var == null) {
log.error("Encoded Username or Password is null!");
throw new IllegalArgumentException("Encoded Username or Password cannot be null.");
}

try {
// 解密
String username_dec = new String(Base64.decodeBase64(username_var), "utf-8");
String password_dec = new String(Base64.decodeBase64(password_var), "utf-8");
log.info("Decoded Username: " + username_dec);
log.info("Decoded Password: " + password_dec);

// 解密后把值赋给username变量
vars.put("username_dec", username_dec);
vars.put("password_dec", password_dec);
} catch (UnsupportedEncodingException e) {
log.error("Character Encoding not supported.", e);
throw new RuntimeException(e);
}

3 获参输出文件

  • 获参输出文件
    • 获取接口返回的列表查询数据,然后以固定的格式输出到文件中。
    • 思路
      • 确保接口能正常访问并查询到数据,使用Json提取器提取想要的参数值。
      • 一个Json提取器只能对应提取一个参数值(表达式无法一次性使用多个)。
      • 使用调试取样器查看提取到的内容,最后使用BeanShell后置处理程序。
      • 在BeanShell后置处理程序中编写脚本,将提取到的内容写入指定文档。

3-1 Json提取值

  • Json提取值
    • 引用名称:自定义,该值即提取值的参数,后续引用使用。
    • Json表达式:$.data[*].app,即获取data下的所有app值。

获参输出文件-Json提取值

3-2 BeanShell

  • BeanShell
    • 使用BeanShell脚本只能提取到Json提取器获取到的第一个app和channel值。
    • 需要加上调试取样器,直接获取app_matchNr和channel_matchNr的总个数。

获参输出文件-BeanShell

3-3 脚本的内容

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 指定需要写入的文件名称
FileWriter file = null;
BufferedWriter out = null;

try {
// 创建一个字符缓存输出流,并设置append为false以覆盖旧数据
file = new FileWriter("D:\\Program\\JMeter\\获参输出文件.txt", false);
out = new BufferedWriter(file);

// 获取匹配项数量
String appMatchNrStr = vars.get("app_matchNr");
String channelMatchNrStr = vars.get("channel_matchNr");

log.info("app_matchNr: " + appMatchNrStr);
log.info("channel_matchNr: " + channelMatchNrStr);

int appMatchNr = (
appMatchNrStr != null && !appMatchNrStr.isEmpty()
) ? Integer.parseInt(appMatchNrStr) : 0;
int channelMatchNr = (
channelMatchNrStr != null && !channelMatchNrStr.isEmpty()
) ? Integer.parseInt(channelMatchNrStr) : 0;

log.info("Number of apps matched: " + appMatchNr);
log.info("Number of channels matched: " + channelMatchNr);

// 确保匹配项数量相同
if (appMatchNr == channelMatchNr) {
// 循环写入每个app和channel值
for (int i = 1; i <= appMatchNr; i++) {
String appValue = vars.get("app_" + i);
String channelValue = vars.get("channel_" + i);
log.info("Writing: " + appValue + "," + channelValue);
out.write(appValue + "," + channelValue + "\n");
}
} else {
log.error("The number of matched app and channel values are not equal!");
}
} catch (IOException e) {
log.error("Error while writing to the file: " + e.getMessage());
} catch (NumberFormatException e) {
log.error("Number format exception: " + e.getMessage());
} finally {
try {
if (out != null) out.close();
if (file != null) file.close();
} catch (IOException e) {
log.error("Error while closing the file: " + e.getMessage());
}
}

JMeter 脚本
https://stitch-top.github.io/2024/06/27/ce-shi-gong-ju/tt01-jmeter/tt02-jmeter-jiao-ben/
作者
Dr.626
发布于
2024年6月27日 21:10:00
许可协议