1. 简介
Apache JMeter 是一款基于Java的开源性能测试工具,专门用于分析和测量Web应用的性能。它允许测试人员模拟服务器、网络或对象上的高负载,以评估系统在不同负载下的整体表现。JMeter提供了直观的GUI界面,用于定义、执行和查看各种负载测试的报告。
虽然JMeter提供了用户友好的GUI来创建和执行测试脚本,但在某些场景下(特别是持续集成/部署流程中),通过Java编程实现自动化会更有优势。
本教程将探讨如何使用Java以编程方式创建和执行Apache JMeter测试脚本,并通过实际示例演示具体步骤。
2. 环境配置
在开始编码前,先确保环境配置正确。我们可以从JMeter官网下载JMeter安装包。JMeter需要Java 8或更高版本支持。在macOS系统上,也可以通过Homebrew安装:
brew install jmeter
还需要在Java项目中配置JMeter依赖。对于Maven项目,在pom.xml中添加以下依赖:
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>5.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>5.6.3</version>
</dependency>
这些依赖包含Apache JMeter核心功能和HTTP组件,提供了创建和运行JMeter测试计划、发送HTTP请求以及处理HTTP响应所需的核心类和工具。
3. 创建测试脚本与环境变量文件
我们将创建一个简单的测试脚本,模拟向指定URL发送HTTP GET请求。该脚本的主要目的是对目标应用(本例为https://www.google.com)进行负载测试。
通过发送多个并发请求模拟真实负载,从而评估应用在不同负载级别下的性能表现。在下面的代码中,首先检查JMETER_HOME环境变量以定位JMeter安装目录。
验证通过后,初始化JMeter引擎StandardJMeterEngine并创建测试计划TestPlan。以下是实现代码:
@Test
void givenJMeterScript_whenUsingCode_thenExecuteViaJavaProgram() throws IOException {
String jmeterHome = System.getenv("JMETER_HOME");
if (jmeterHome == null) {
throw new RuntimeException("JMETER_HOME environment variable is not set.");
}
String file = Objects.requireNonNull(JMeterLiveTest.class.getClassLoader().getResource("jmeter.properties")).getFile();
JMeterUtils.setJMeterHome(jmeterHome);
JMeterUtils.loadJMeterProperties(file);
JMeterUtils.initLocale();
StandardJMeterEngine jmeter = new StandardJMeterEngine();
HTTPSamplerProxy httpSampler = getHttpSamplerProxy();
LoopController loopController = getLoopController();
ThreadGroup threadGroup = getThreadGroup(loopController);
TestPlan testPlan = getTestPlan(threadGroup);
HashTree testPlanTree = new HashTree();
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
threadGroupHashTree.add(httpSampler);
SaveService.saveTree(testPlanTree, Files.newOutputStream(Paths.get("script.jmx")));
Summariser summer = null;
String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary");
if (summariserName.length() > 0) {
summer = new Summariser(summariserName);
}
String logFile = "output-logs.jtl";
ResultCollector logger = new ResultCollector(summer);
logger.setFilename(logFile);
testPlanTree.add(testPlanTree.getArray()[0], logger);
jmeter.configure(testPlanTree);
jmeter.run();
System.out.println("Test completed. See output-logs.jtl file for results");
System.out.println("JMeter .jmx script is available at script.jmx");
}
在*getLoopController()*方法中,循环控制器控制测试的迭代次数。这里配置为只执行一次:
private static LoopController getLoopController() {
LoopController loopController = new LoopController();
loopController.setLoops(1);
loopController.setFirst(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
loopController.initialize();
return loopController;
}
*getThreadGroup()*方法定义了一个名为"Sample Thread Group"的线程组,指定了虚拟用户/线程数量和启动时间。该线程组包含10个线程(虚拟用户),在5秒内逐渐启动:
private static ThreadGroup getThreadGroup(LoopController loopController) {
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("Sample Thread Group");
threadGroup.setNumThreads(10);
threadGroup.setRampUp(5);
threadGroup.setSamplerController(loopController);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
return threadGroup;
}
在getHttpSamplerProxy()方法中,创建HTTP采样器HTTPSamplerProxy,向目标URL(https://www.google.com)发送GET请求。配置域名、路径和请求方法:
private static HTTPSamplerProxy getHttpSamplerProxy() {
HTTPSamplerProxy httpSampler = new HTTPSamplerProxy();
httpSampler.setDomain("www.google.com");
httpSampler.setPort(80);
httpSampler.setPath("/");
httpSampler.setMethod("GET");
httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
return httpSampler;
}
通过HashTree构建测试计划,添加线程组和HTTP采样器:
private static TestPlan getTestPlan(ThreadGroup threadGroup) {
TestPlan testPlan = new TestPlan("Sample Test Plan");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
testPlan.addThreadGroup(threadGroup);
return testPlan;
}
最后配置JMeter引擎并执行测试。
该脚本模拟了10个并发用户在5秒内逐渐启动的负载场景。每个用户执行一次HTTP GET请求。由于未配置多次迭代,负载保持稳定。
jmeter.properties文件用于配置JMeter的测试执行参数。以下属性指定了测试结果的保存格式:
jmeter.save.saveservice.output_format=xml
4. 理解输出文件
我们将测试计划保存为script.jmx文件(可在JMeter GUI中加载执行),同时配置了Summarizer汇总测试结果,并通过结果收集器将结果保存到output-logs.jtl文件。
4.1 理解.jtl文件
output-logs.jtl文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<testResults version="1.2">
<httpSample t="354" it="0" lt="340" ct="37" ts="1711874302012" s="true" lb="" rc="200" rm="OK" tn="Sample Thread Group 1-1" dt="text" by="22388" sby="111" ng="1" na="1">
<java.net.URL>http://www.google.com/</java.net.URL>
</httpSample>
<httpSample t="351" it="0" lt="317" ct="21" ts="1711874302466" s="true" lb="" rc="200" rm="OK" tn="Sample Thread Group 1-2" dt="text" by="22343" sby="111" ng="1" na="1">
<java.net.URL>http://www.google.com/</java.net.URL>
</httpSample>
<httpSample t="410" it="0" lt="366" ct="14" ts="1711874303024" s="true" lb="" rc="200" rm="OK" tn="Sample Thread Group 1-3" dt="text" by="22398" sby="111" ng="1" na="1">
<java.net.URL>http://www.google.com/</java.net.URL>
</httpSample>
<httpSample t="363" it="0" lt="344" ct="19" ts="1711874303483" s="true" lb="" rc="200" rm="OK" tn="Sample Thread Group 1-4" dt="text" by="22367" sby="111" ng="1" na="1">
<java.net.URL>http://www.google.com/</java.net.URL>
</httpSample>
</testResults>
每个*
属性 | 说明 |
---|---|
t |
总耗时(毫秒) |
it |
空闲时间(等待响应时间) |
lt |
延迟(请求往返时间,不含空闲时间) |
ct |
连接耗时(建立连接时间) |
ts |
请求时间戳(Unix时间戳) |
s |
请求状态(true/false) |
lb |
采样器标签 |
rc |
HTTP响应码 |
rm |
响应消息 |
tn |
执行线程名称 |
dt |
响应数据类型(如text/binary) |
by |
响应体字节数 |
sby |
请求体字节数 |
ng/na |
当前/全局活跃线程数 |
这些数据对分析系统性能、识别瓶颈和优化应用至关重要。
4.2 理解.jmx文件
.jmx文件是JMeter测试计划的XML配置文件。script.jmx内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1">
<org.apache.jorphan.collections.HashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Sample Test Plan"/>
<org.apache.jorphan.collections.HashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Sample Thread Group">
<intProp name="ThreadGroup.num_threads">10</intProp>
<intProp name="ThreadGroup.ramp_time">5</intProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">1</intProp>
</elementProp>
</ThreadGroup>
<org.apache.jorphan.collections.HashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">www.google.com</stringProp>
<intProp name="HTTPSampler.port">80</intProp>
<stringProp name="HTTPSampler.path">/</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
</HTTPSamplerProxy>
<org.apache.jorphan.collections.HashTree/>
</org.apache.jorphan.collections.HashTree>
</org.apache.jorphan.collections.HashTree>
</org.apache.jorphan.collections.HashTree>
</jmeterTestPlan>
关键元素说明:
- **
<TestPlan>
**:测试计划根元素 - **
<ThreadGroup>
**:定义虚拟用户组(线程数、启动时间) - **
<LoopController>
**:控制执行流程(循环次数) - **
<HTTPSamplerProxy>
**:定义HTTP请求细节(域名、端口、路径、方法)
这些元素共同构成了JMeter性能测试的完整蓝图,支持模拟用户交互并分析服务器响应。
5. 总结
本文演示了如何使用JMeter Java API以编程方式创建和执行测试脚本。通过这种方式,开发者可以自动化性能测试并将其无缝集成到开发流程中。
⚠️ 注意:这种方法特别适合在CI/CD流水线中实现自动化测试,帮助在开发早期发现性能瓶颈。
✅ 优势:
- 实现测试自动化
- 与开发流程深度集成
- 早期发现性能问题
❌ 局限:
- 需要Java编程基础
- 调试比GUI模式复杂
本文的完整源代码可在GitHub获取。