一、背景
在利用JMeter实施测试的时候,除了下列JMeter支持的标准协议之外(HTTP/HTTPS,FTP,JDBC,JMS,SOAP和TCP等),可能需要测试其他协议,也可能处理一些特殊的业务逻辑,如:加密解密等,而且JMeter自带的协议根本不能满足我们测试的需要。这时候就需要我们对JMeter进行扩展,以满足我们的测试需求了。
JMeter自定义扩展有很多种,分为自定义函数、自定义Java Sampler、BeanShell、自定义Sampler等等,本次主要说一说前2个。
二、自定义函数
JMeter 自带很多函数,基本上能满足我们日常测试的使用,但会存在自带函数不适合测试需求的情况,如:我们在测试业务时,有时候就需要对数据进行特别处理或者处理业务逻辑时,这时候就需要使用自定义的函数。
2.1 准备开发环境
2.1.1 新建JMeter项目
打开 IDE(我使用的是 IDEA),新建一个 maven 工程。扩展函数的Java类的报名必须是.functions
,所以在工程的目录下新增包名functions
,如图所示
2.1.2 配置 pom.xml 文件
在配置文件的dependencies
节点添加 JMeter 的依赖文件。JMeter 版本需要根据你使用的版本来决定。
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_functions</artifactId>
<version>${jmeter.version}</version>
</dependency>
2.2. 函数实现
2.2.1 新增你的自定义函数类
在刚才创建的functions
文件夹下新建你的自定义函数类并继承父类AbstractFunction
,比如:com.dc.functions
,如图所示
下一步,就是我们往这个函数类填充我们需要实现的业务逻辑或者处理数据的方法。
2.2.2 实现函数逻辑
AbstractFunction
抽象类提供4个抽奖方法,在扩展的时候需要一一进行实现。
2.2.2.1 变量部分
log
可选。该部分是运行函数时,输出日志到 JMeter 整体日志。
desc
必填,是函数的参数名字,根据需求,填写多少个。
KEY
必填,是显示的函数名称。
values
必填,声明,表示输入的函数值。
2.2.2.2 execute方法
public String execute(SampleResult sampleResult, Sampler sampler) throws InvalidVariableException
JMeter 会将上次运行的SampleResult和当前的Sampler作为参数传入到该方法里,返回值就是在运行该function后得到的值,以String类型返回。
2.2.2.3 setParameters 方法
public void setParameters(Collection<CompoundVariable> collection) throws InvalidVariableException
这个方法在用于传递用户在执行过程当中传入的实际参数值。该方法在function没有参数情况下也会被调用。一般该方法传入的参数会被保存在类内全局变量里,并被后面调用的execute方法中使用到。另外,我们也可以增加对传入参数的检查机制,防止出现错误。
checkMinParameterCount
检查最少需要输入的参数数量,达不到就自动报错。
checkParameterCount
检查输入的参数个数是否符合给定的数量或者范围,不符合报错。
this.values = collection.toArray();
将输入的参数值传递到value
。
2.2.2.4 getReferenceKey
public String getReferenceKey()
返回就是函数的名字。JMeter的命名规则是在函数名前面加入双下划线__
。比如__AddInt
,函数的名字需要类名应该一致,而且该名字应该以static final
的方式在实现类定义好,防止在运行过程中被修改。
2.2.2.5 getArgumentDesc
public List<String> getArgumentDesc()
告诉 JMeter 关于你实现函数的描述。
2.2.2.6 最终源代码实现
实现的源代码如下所示,重要的代码已经有注释。
package com.dc.functions;
import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.functions.AbstractFunction;
import org.apache.jmeter.functions.InvalidVariableException;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.threads.JMeterVariables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class AddInt extends AbstractFunction {
private static final Logger log = LoggerFactory.getLogger(AddInt.class);
//显示的参数名字
private static final List<String> desc = new LinkedList<>();
static {
desc.add("First int");
desc.add("Second int");
desc.add("Third int");
desc.add("Result Int");
}
/**
* 显示的函数名字
*/
private static final String KEY = "__AddInt";
/**
* 参数值
*/
private Object[] values;
@Override
public String execute(SampleResult sampleResult, Sampler sampler) throws InvalidVariableException {
JMeterVariables localJMeterVariables = getVariables();
//第一个整数
String firstInt = ((CompoundVariable)this.values[0]).execute();
//第二个整数
String secondInt = ((CompoundVariable)this.values[1]).execute();
//第三个整数
String thirdInt = ((CompoundVariable)this.values[2]).execute();
String sumString = "";
int a = 0;
int b = 0;
int c = 0;
try {
a = Integer.parseInt(firstInt);
b = Integer.parseInt(secondInt);
c = Integer.parseInt(thirdInt);
sumString = Integer.toString(this.addInt(a, b, c));
} catch (NumberFormatException e) {
log.error(e.toString());
}
if ((localJMeterVariables != null) && (this.values.length > 0)) {
localJMeterVariables.put(((CompoundVariable)this.values[values.length - 1]).execute(), sumString);
}
return sumString;
}
//设置参数值
@Override
public void setParameters(Collection<CompoundVariable> collection) throws InvalidVariableException {
checkMinParameterCount(collection, 3);
checkParameterCount(collection, 3);
this.values = collection.toArray();
}
//返回函数名字
@Override
public String getReferenceKey() {
return KEY;
}
//返回参数名字
@Override
public List<String> getArgumentDesc() {
return desc;
}
public int addInt(int a, int b, int c) {
return a + b + c;
}
}
2.2.3 打包和部署
2.2.3.1 生成 jar 包
代码经单元测试没问题后,执行命令生成 jar 包。可使用mvn package
等命令。
2.2.3.2 将 jar 放入到 JMeter
将生成的 jar 包及其依赖的 jar 包,一并放入到JMETER_HOME/lib/ext/
路径中,重启 JMeter 即可。也可以将生成的-jar-with-dependencies.jar
放入到上述指定的 JMeter 目录中。
2.2.3.3 使用扩展的函数
成功重启 JMeter 后,在函数助手即可我们自定义扩展的函数。在对应的参数名称右边输入需要传入的参数值即可。编写后,复制 Function syntax 输入框的内容即可。
2.2.3.4 测试自定义函数
最后我们创建一个测试,使用Dummy Sampler,在request使用该函数,并添加查看结果树的监听器。运行成功后,在监听器上得到6即可。
三、自定义Java Sampler
3.1 背景
在利用JMeter实施性能测试的时候,除了下列JMeter支持的标准协议之外(HTTP/HTTPS,FTP,JDBC,JMS,SOAP和TCP等),可能需要支持一些别的协议,本文介绍了如何利用JMeter提供的Java Sampler扩展机制来方便地扩展对新协议的支持。除了本文介绍的Java Sampler扩展方式之外,更加标准的方式是扩展标准的Sampler。
3.2 简介
JMeter跟商业化的软件相比,有些表达方式比较拗口、难以理解,比如我们这篇文章里的主角“Java Sampler”。如果只是看文字的表面意思读者可能会一头雾水,不知道这是个什么东东。Sampler的中文字面翻译是“采样机”或者“取样器”,在JMeter的中文版本里对这个Sampler没有做任何翻译,可能当时中文版本的翻译者对这个如何翻译也不太确信,干脆直接在菜单里就不翻译了。Sampler在别的类似的产品中,更为普通的表达方式是“协议”。不过Java Sampler的意思不是指支持所谓的“Java协议”,或者说“Java取样器”,比较准确的意思是利用自定义的Java类来扩展对新协议的支持,这些扩展的协议通过“Java请求”加入到测试脚本中,如下面两个步骤所示
使用步骤也如以上两个步骤操作即可。
3.3 准备开发环境
查看 JMeter 自定义函数部分的开发环境准备,导入相应的jar包依赖即可。需要注意的是除了引用“ApacheJMeter_core”和“ApacheJMeter_java”之外,还需要引入实现协议所关联的库。
3.4 开发 Java Sampler
开发自己的Java Sampler
需要继承并扩展JMeter的AbstractJavaSamplerClient
类,并实现从父类继承过来的抽奖方法即可。
3.4.1.1 runTest方法
public SampleResult runTest(JavaSamplerContext javaSamplerContext)
这是必须实现的方法。runTest方法的返回结果为SampleResult,就是每次调用返回的结果。这个方法的实现逻辑一般如下:
SampleResult result=newSampleResult();
result.sampleStart();
try{
...
//发出请求
result.sampleEnd();
//请求成功,设置测试结果为成功
result.setSuccessful(true);
result.setResponseData("data...");
result.setResponseMessage("message..)";
result.setResponseCodeOK();
}catch(Exception e){
//请求失败,设置测试结果为失败
result.sampleEnd();
result.setSuccessful(false);
result.setResponseCode("500");
...
}
return result;
如上述代码所示,代码逻辑主要是:
1)对目标系统发送正确的协议数据。
2)根据目标系统返回的数据,给SampleResult设置正确的开始、结束时间,这样JMeter引擎知道测试成功与否,并正确地显示到JMeter的报告结果中。
3.4.1.2 getDefaultParameters方法
public Arguments getDefaultParameters()
在运行测试的时候,需要用户提供一些输入,这些输入甚至可能是JMeter的变量,这个方法的用处就在于允许用户在测试开始,或者运行期间指定变量的值。如下图所示的sampler例子就暴露了一些参数,允许测试人员在测试编辑和执行期间更改参数值。
3.4.1.3 setupTest方法
public void setupTest(JavaSamplerContext context)
跟写JUnit测试的setup方法类似,这里主要运行一些针对一个虚拟用户的一次性起始、准备性的操作,比如建立连接的过程在测试阶段可能只需要一次,那么这段建立连接的逻辑可以放在该方法里。
3.4.1.4 teardownTest方法
public void teardownTest(JavaSamplerContext context)
与JUnit测试的teardown方法类似,这里主要运行一些针对一个虚拟用户的收尾的操作,比如清除连接等。这里需要注意的是,该方法的调用不是在单个虚拟用户的线程里执行的操作,而是所有虚拟用户在一个线程里顺序执行的。
3.5 打包、部署
完成了代码的编写,需要将代码进行编译和部署。执行 mvn package
命令,在target目录下会生成一个jar包。请注意在本文所提供的pom.xml里,编译出来的jar包里包含了所需的第三方jar包,比如说fusesource的mqtt-client库,这样避免在JMeter运行的时候单独去寻找别的jar包,使得部署过程更加方便。
将编译好的target/jmeter-java-sampler-version-jar-with-dependencies.jar拷贝到$JMETER_HOME/lib/ext目录下,如果开着JMeter则重启,启动完毕,加入一个Java请求,在“类名称”下拉列表框中应该能看到新扩展的类了。如果不能发现,请查看一下lib/ext目录下是否拷贝了jar包,再者也可以看一下JMeter的日志,确认没有报出异常。
四、总结
在JMeter中,自定义扩展可以帮我们实现自家公司的业务逻辑,实现JMeter本身没有自带的功能,这可帮助我们更加高效地进行测试。