一、背景
现在有一个java的sdk,我想测试sdk接口的性能,因为这不是一个http请求,我无法直接使用jmeter的http请求取样器去进行测试,这时就需要使用jmeter的java请求取样器,他需要我们编写java代码来实现调用逻辑,jmeter来帮助我们去实际调用,我们依然可以使用其汇总结果或其他取样器来测试接口的性能指标,类似的需求还有当接口是自定义的tcp接口时也可以采用此种方式。
二、使用步骤
- 创建基于Maven的java项目
- 引用对应Jmeter版本的ApacheJMeter_Java,我用的是Jmeter 5.4.1, 所以这里就pom.xml配置如下:
1. ApacheJmeter_java是Apache JMeter的Java模块的依赖项。Apache JMeter是一个用于性能测试和负载测试的开源工具。它提供了一个用户友好的图形界面,用于创建和运行各种类型的测试计划。ApacheJMeter_java模块包含了JMeter的Java相关功能和类库。它提供了一些Java相关的操作和功能,例如与Java相关的断言、前置处理器、后置处理器等。这个模块允许用户在测试计划中使用Java编程语言的能力来自定义测试脚本和逻辑。<dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_java</artifactId> <version>5.4.1</version> </dependency> <dependency> <groupId>org.apache.jmeter</groupId> <artifactId>ApacheJMeter_core</artifactId> <version>5.4.1</version> </dependency>
2. ApacheJMeter_core是Apache JMeter的核心模块的依赖项。Apache JMeter是一个用于性能测试和负载测试的开源工具。它提供了一个用户友好的图形界面,用于创建和运行各种类型的测试计划。ApacheJMeter_core模块包含了JMeter的核心功能和类库,包括线程组、采样器、断言器、监听器等。它是构建和执行性能测试的基础。 - 编写代码
在java目录下新建一个class文件,此处implements JavaSamplerClient接口或者是extends AbstractJavaSamplerClient父类 都可以,实现抽象方法runTest,该方法即为jmeter实际调用的功能测试方法,该抽象类还有其他默认实现的方法,在下方示例代码中做了简单的解释供参考。
package com.fkp.jmeter.test; import org.apache.jmeter.config.Arguments; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; import java.nio.charset.StandardCharsets; public class TestDemo extends AbstractJavaSamplerClient { private String name; private int age; /** * 定义默认的参数,在jmeter中会自动显示在这里定义的默认参数 * @return Arguments对象 */ @Override public Arguments getDefaultParameters() { Arguments arguments = new Arguments(); arguments.addArgument("name", "zhangsan"); arguments.addArgument("age", "24"); return arguments; } /** * 在执行测试之前执行,只执行一次,可以获取传递过来的参数,一般用来做初始化操作 * @param context 可以获取jmeter传递过来的参数 */ @Override public void setupTest(JavaSamplerContext context) { //对成员变量初始化 name = context.getParameter("name"); age = context.getIntParameter("age"); } /** * 真正的测试代码块,jmeter按照配置的逻辑执行该方法 * @param javaSamplerContext 上下文对象,可以获取jmeter传递的参数 * @return SampleResult对象用于封装结果 */ @Override public SampleResult runTest(JavaSamplerContext javaSamplerContext) { SampleResult sr = new SampleResult(); //设置标签 sr.setSampleLabel("testDemo"); //设置开始标志,否则无法统计tps等 sr.sampleStart(); try { //执行业务逻辑 String data = "name: " + name + ", age: " + age; sr.setResponseCodeOK(); sr.setResponseData(data, StandardCharsets.UTF_8.displayName()); sr.setSuccessful(true); }catch (Throwable e){ e.printStackTrace(); sr.setResponseCode("500"); sr.setResponseData(e.getMessage(), StandardCharsets.UTF_8.displayName()); sr.setSuccessful(false); }finally { sr.sampleEnd(); } return sr; } /** * 测试完成后执行,一般用于关闭资源 * @param context 上下文对象,可用于获取jmeter传递的参数 */ @Override public void teardownTest(JavaSamplerContext context) { System.runFinalization(); } }
Jmeter自带的jar包给我们提供了4个方法,可根据实际需要完善对应的方法,以实现对被测程序的压测。
a--> getDefaultParameters方法
这个方法里,可以设置各种参数,这些参数可以从GUI处获取,也可以设置合适的默认值。
b--> setupTest方法
每个线程会执行1次,用于初始化参数
c--> runTest方法
调用被测程序的主方法,通过该方法入参javaSamplerContext可以获取到jmeter传入的参数
d--> teardownTest方法
测试结束时调用,每个线程只执行一次。
继承jmeter的AbstractJavaSamplerClient父类,继承后重载父类中的方法:
类名 方法名 说明 Arguments params = new Arguments();
params.addArgument("Sleep_Time","100");
添加到参数的key和value,这个参数添加后在java request的参数中显示
JMeterVariables jmeterVar = javaSamplerContext.getJMeterVariables();
jmeterVar.get("username")
获取jmeter全局变量中的值,比如获取界面上用户自定义变量配置元件中的变量值,csv导入数据的变量等,通过变量名获取
SampleResult sampleResult = new SampleResult();
sampleResult.sampleStart();
在请求开始之前记录一下开始时间,然后在请求结束后在记录结束时间sampleResult.sampleEndt();,就能统计出每次请求所需的时间
sampleResult.setSuccessful(true或者false);
记录请求的结果为成功或失败为true则记录成功,为false则记录为失败
sampleResult.sampleEnd();
在请求开始之前记录一下开始时间,然后在请求结束后在记录结束时间sampleResult.sampleEndt();,就能统计出每次请求所需的时间
sampleResult.setResponseData("".getBytes());
获取每次请求返回的结果
- 通过maven编译打包生成一个jar包
生成的jar复制到对应jmeter的lib/ext目录下
- 创建Jmeter压测脚本
打开jmeter -> 添加线程组 -> 添加java请求取样器 -> 添加结果树(汇总报告等)。
在java请求取样器中可以选择demo.jar中继承了AbstractJavaSamplerClient的测试类,选中后即可看到请求的默认参数(如果有的话),之后就和测试http接口相同了。
- 开始执行脚本
将线程组设置为3个并发,循环5次,如图: 结果树组件中,我们可以看到,执行了15次请求,Response data中打印出了我们在runTest中指定的返回内容。
三、 FAQ
1. 每次我们用maven打完jar包之后,都有手动拷贝,比较麻烦
在maven的pom.xml中加上如下代码,指定jar拷贝目录,直接在打包的同时,就将jar包自动拷贝到了jmeter安装目录下的/lib/ext文件夹中。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>copy-files</id> <phase>package</phase> <configuration> <tasks> <!--将生成的项目jar拷贝到jmeter的lib/ext目录下,同时修改Jmeter安装目录 --> <copy file="${project.build.directory}/JmeterTestForSDK-0.0.1-SNAPSHOT.jar" tofile="D:\apache-jmeter-2.13\lib\ext\JmeterTestForSDK-0.0.1-SNAPSHOT.jar" overwrite="true"/> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
2. moven打包时,默认不会将第三方依赖包打进来
在 pom.xml 中添加maven-assembly 插件实现… 打出的包里 xxxx-with-dependencies.jar 就是包含依赖包默认打包jar<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <appendAssemblyId>false</appendAssemblyId> <!--jar包名称--> <finalName>${project.artifactId}-${project.version}</finalName> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <!--添加项目中主类--> <mainClass>com.example</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>assembly</goal> </goals> </execution> </executions> </plugin> </plugins> </build>