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>

每个**元素代表一次HTTP请求,包含以下关键属性:

属性 说明
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获取。


原始标题:Create and Run Apache JMeter Test Scripts via Java Program | Baeldung