🍕 通过使用脚本来执行高级操作,例如:创建复杂的测试场景、自定义请求参数、处理响应数据、编写逻辑控制和循环等。
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值)。
调用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("调试日志---------------------" ); log.info(props.get("jmeter.version" ));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();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;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-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;String username_enc_var = vars.get("username_enc" );String password_enc_var = vars.get("password_enc" ); 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); 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值。
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 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()); } }