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值)。
    • 调用Java源码方式
      • 通过BeanShell取样器直接执行Java源码、引入外部源码文件source(path):源码绝对路径。
      • 引入外部class文件addClassPath(path):指引入Java源码编译后的.class后缀可执行文件。
      • 引入外部jar包(推荐使用):放在JMeter的lib目录下,或测试计划添加,或import直接导入。
    • 结果断言(后置处理)、Base64加密(前置处理)、Base64解密(后置处理)。

2-1 打印信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 打印变量
log.info("打印变量---------------------");

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

// 获取JMeter属性值
log.info(props.get("jmeter.version"));
// 修改JMeter属性值,不常用,实际不会频繁调整文件属性值
String version = props.put("jmeter.version", "5.3");
log.info(version);

2-2 结果断言

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.apache.jmeter.samplers.SampleResult;

SampleResult previousResult = prev.getSampleResult();
String response = previousResult.getResponseDataAsString();

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

2-3 Base64加密

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-4 Base64解密

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_enc_var = vars.get("username_enc");
String password_enc_var = vars.get("password_enc");

// 查看log日志,判断加密后的username的值是否正确
log.info("Encoded Username: " + username_enc_var);
log.info("Encoded Password: " + password_enc_var);

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

try {
// 解密
String username_str = new String(Base64.decodeBase64(username_enc_var), "utf-8");
String password_str = new String(Base64.decodeBase64(password_enc_var), "utf-8");
log.info("Decoded Username: " + username_str);
log.info("Decoded Password: " + password_str);

// 解密后把值赋给username变量
vars.put("username", username_str);
vars.put("password", password_str);
} 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
// 指定需要写入的文件名称
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/tt01-02-jmeter-jiao-ben/
作者
Dr.626
发布于
2024年6月27日 21:10:00
许可协议