🍕 通过使用脚本来执行高级操作,例如:创建复杂的测试场景、自定义请求参数、处理响应数据、编写逻辑控制和循环等。
1 获取验证码
获取验证码
(组件)HTTP请求:获取验证码图片路径并进行请求。
(组件)保存响应到文件:保存验证码图片到本地电脑。
(组件)OS进程取样器:加载Python脚本,再进行识别。
(组件)正则表达式提取器:提取脚本识别到的验证码。
验证码一般用在需要验证用户身份或防止机器人自动化操作的地方。
常见的应用场景包括:用户注册、登录、密码重置、支付验证等等。
日常测试时,一般不提倡获取验证码测试(目的不在验证用户身份)。
1-1 Python脚本文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import ddddocr ocr = ddddocr.DdddOcr()with open ("D:\\Program\\JMeter\\验证码.jpeg" , "rb" ) as f: img_bytes = f.read() 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
。
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("调试日志---------------------" ); log.info("获取到的JMeter属性值:" + props.get("jmeter.version" )); props.put("jmeter.version" , "5.7" );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" )); 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();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;String username_var = vars.get("username" );String password_var = vars.get("password" ); 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); 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;String username_var = vars.get("username" );String password_var = vars.get("password" ); 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); 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值。
3-2 BeanShell
BeanShell
使用BeanShell脚本只能提取到Json提取器获取到的第一个app和channel值。
需要加上调试取样器,直接获取app_matchNr和channel_matchNr的总个数。
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 { 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) { 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()); } }