性能系列文章目录
欢迎大家关注「海拉鲁知识大陆」 多交流不迷路
第三章-性能测试之JMeter自定义-后置处理器二开
一、背景
基于使用JMeter脚本的做自动化测试过程中,我们测试脚本也需要健壮性的考虑,重试机制是一种容错设计模式,当操作失败时(例如网络请求超时、数据库连接失败等),增加合理的重试机制可以提高我们脚本的可用性、稳定性和真实性。
一个脚本的重试机制通常包含以下几个关键要素:
- 重试触发条件:何种异常或错误状态下需要进行重试
- 重试次数:最大允许重试的次数
- 重试间隔:两次重试之间的时间间隔
方式1:
二、原有组件实现重试
基于Jmeter脚本原有的组件的情况,我们整理大体思路如下:
- 初始化标志位状态、参数化最大次数(方便修改)
- 使用While控制器判断标志位状态实现重试请求
- 使用JSR223后置处理程序重试断言(包括是否需要解密等)、重试最大次数等逻辑实现控制赋值标志位状态
预处理器代码实现如下
import groovy.json.JsonSlurper;
import cn.hrfax.securityNew.GatewayAesUtils;
String samplerName = ctx.getCurrentSampler().getName();
String threadNum = ctx.getThreadNum();
String response_data = prev.getResponseDataAsString();
String code = prev.getResponseCode();
String result1 = "\"msg\":\"成功\""; //为正常请求响应中请求成功标签,需要根据每个请求响应不同进行修改
int maxRetries = Integer.parseInt(vars.get("maxRetries")); // 最大重试次数
int currentRetry = Integer.parseInt(vars.get("retryCount")); // 获取当前重试次数
boolean isFailed = false;
// 条件1: 响应码非200
if (!code.equals("200")) {
isFailed = true;
vars.put("status","pending");
}
// 条件2: 响应格式不正确,有没有data和sign
if (!isFailed) {
def responseObj = new JsonSlurper().parseText(response_data);
if (responseObj.containsKey("data") && responseObj.containsKey("sign")){
isFailed = false;
}else{
isFailed = true;
vars.put("status","pending");
}
}
// 条件3: 断言内容失败
if (!isFailed) {
String dec_public_key = vars.get("DEC_PUBILC_KEY");
def decryptData = GatewayAesUtils.decrypt(response_data,dec_public_key);// 解密响应
if(!decryptData.contains(result1)){
isFailed = true;
vars.put("status","pending");
} else {
// 断言成功,重置计数器
isFailed = false;
vars.put("status","active");
vars.put("retryCount", "0");
}
}
// 若失败且未达最大重试次数
if (isFailed && currentRetry < maxRetries) {
currentRetry++;
vars.put("retryCount", String.valueOf(currentRetry)); // 更新计数器
vars.put("status","pending");
log.info("Retry重试 # " + currentRetry + " for failed request"+" ${samplerName}");
} else {
// 重试成功或已达上限,重置计数器
vars.put("status","active");
vars.put("retryCount", "0");
}
那么以上 针对于单个接口进行重试控制貌似问题不大,当我们存在需要连续且多个接口需要重试,第1个While脚本最后处理志标准位赋值为成功,初始化是依托于取样器的运行,导致不能进入第2个While而跳过接口,虽然可以使用2个不同的标志位进行交替While判断使用来避免、但稳定性中全流程脚本接口需要大量重试,处理起来真是苦不堪而且脚本看起来冗余又繁重,本着性能脚本也需要简洁和优雅原则,急需二开一个后置处理器来完成实现请求重试机制。
那么它来了!
方式2:
三、后置处理器二开实现重试
- 重试条件-参数说明
Max Number of Retries: 最大重试次数
Response Part: 响应断言选择部分,支持Response code, data, headers, message可选
Error Pattern: 断言内容,如果响应断言部分匹配到断言内容,则不需要重试
- 解密设置-参数说明
Data Decrypt: 选择ResponseData是否解密
Decrypt Pub: 解密使用的Pubilc Key
- 延时设置-参数说明
Pause (milliseconds): 在重试采样器之前暂停多长时间
Backoff: 可修改每次重试后的初始暂停,以减慢速度避免过载
Jitter Factor (positive decimal):时间抖动因子,添加到暂停中的随机变化量(默认值:0,即抖动关闭)。例如,值为0.1时,计算出的延迟将加上暂停的10%。
Respect "Retry-After": 是否遵从响应头“Retry After”字段,在重试之前是否遵从HTTP响应标头“Retry After”(默认值:False)。
将jmeter-retrier-1.0放至Jmeter安装目录中\lib\ext目录下重启即可使用
1.重试使用效果如下(后置重试处理器是重启sampler覆盖修改原来SampleResult的方式,即在结果树和聚合报告中是展示是最新一次请求的结果,可在打印日志可查看重试情况)
2.无需重试以及响应解密效果如下
四、自定义后置处理器-二次开发核心代码说明:
开发环境准备,引入核心jar,打包使用等这里不再赘述见自定义开发函数篇,直接进入核心代码部分
1.选择一个包并编写三个文件:
[组件名称].java-----RetryPostProcessor.java
[组件名称]BeanInfo.java-----RetryPostProcessorBeanInfo.java
[组件名称]Resources.properties-----RetryPostProcessorResources.properties
2.RetryPostProcessor.java必须实现TestBean接口。
3.RetryPostProcessorBeanInfo.BeanInfo.java应该扩展org.apache.jmeter.testbeans.BeanInfoSupport
4.实现具体处理逻辑。
代码实现情况如下:
- [组件名称]BeanInfo.java 表示PostProcessor的界面,如本例中的RetryPostProcessorBeanInfo;
创建一个零参数构造函数,我们在其中调用super(RetryPostProcessor.class); 其中
/**为每个属性设置属性
* NOT_UNDEFINED
* 该属性不会保留为null。
* DEFAULT
* 如果NOT_UNDEFINED为true ,则必须给出默认值。
* NOT_EXPRESSION
* 如果为true ,则不会为函数解析该值。
* NOT_OTHER
* 这不是一个自由形式的输入字段——必须提供一个值列表。
* TAGS
* 使用String[]作为值,这将设置可接受值的预定义列表,JMeter 将创建一个下拉选择。
*/
- [组件名称].java 表示插件的处理逻辑,如本例中的RetryPostProcessor.java;
这块将扩展AbstractTestElement并实现PostProcessor,(不需要迭代的不用实现)TestBean是一个标记接口,因此没有要实现的方法。扩展AbstractTestElement将使我们的组件成为测试计划中的后置处理器。重写process()方法实现我们重试逻辑
其中封装必要的实现方法,重试条件、暂停间隔时间、取样器结果修改、ResponsePart枚举、BackoffType枚举等
- [组件名称]Resources.properties表示界面显示的文字,如本例中RetryPostProcessorResources.properties,其中:
/**定义界面所有字符串资源
* displayName
* 这将为将出现在菜单中的元素提供一个名称。
* json_data.displayName
* 我们创建了一个名为csv_data的属性分组,因此我们必须为分组提供一个标签
* filename.displayName
* 文件名输入元素的标签。
* filename.shortDescription(当鼠标定位到GUI输入框名称提示性文字)
* 类似工具提示的帮助文本简介。
* variableNames.displayName
* 变量名称输入元素的标签。
* variableNames.shortDescription
* variableNames输入元素的工具提示。
*/