Run JMeter tests in Java code
In this blog post, we will run JMeter tests from code. First we will look at how we can create an IntelliJ project in Java to build a JMeter performance test.
Once you understand how JMeter tests in code can be written you can start to build tests in your development code base to compliment unit tests or functional tests written by developers during the creation of application and services.
In a DevOps world it is the responsibility of developers to support all development effort with a suitable and robust set of tests that are run as the code is built and deployed into the various non-prod and prod style environments and your performance tests can sit alongside these.
We have chosen to use IntelliJ due to its widespread use and we have chosen to create a Maven project to allow us to import the JMeter dependencies.
Clearly your preference may be different, but the principles remain the same.
We will build and execute this as a stand-alone project to demonstrate the principles but once you are happy with how it works there is no reason you cannot build similar performance tests in your application development codebase or even pair with the development teams to build a performance capability in your deployment pipelines.
Creating the project¶
The community edition of IntelliJ can be found here and is open source. Create a new project and select Maven:
In the new project there will be a pom.xml file, and we need to add several dependencies in here to support the execution of JMeter in Java code.
These dependencies are:
<dependencies>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.20.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
These are not only the JMeter dependencies but also the log4j ones to support the outputting of test results.
Once you have added these dependencies you will see a Maven update icon in IntelliJ, click on this to import all the dependencies:
Once this is completed you need to copy the log4j2.xml
file from your JMeter bin folder to the resources folder in your IntelliJ project, again this is to support the logging from the test you are about to create:
Creating a simple test¶
We can now build a JMeter test in our project. In the java folder under main we are going to create a Java class file; we will call this JMeterExampleOne
.
Let’s start adding the Java code to this class file, the full class file for this example is below.
The first thing we add is:
public static void main(String[] args) throws Exception {
// Create the Jmeter engine
StandardJMeterEngine jm = new StandardJMeterEngine();
// Set some configuration
String jmeterHome = "\\apache-jmeter-5.1.1\\apache-jmeter-5.4.1";
JMeterUtils.setJMeterHome(jmeterHome);
JMeterUtils.loadJMeterProperties(jmeterHome + "/bin/jmeter.properties");
JMeterUtils.initLocale();
// Create a new hash tree to hold our test elements
HashTree testPlanTree = new HashTree();
We create the JMeter engine and set some configuration. We also add a hash tree which we will add our test element to. We now create a HTTP Sampler:
// Create a sampler
HTTPSampler httpSamplerOne = new HTTPSampler();
httpSamplerOne.setDomain("octoperf.com");
httpSamplerOne.setPort(443);
httpSamplerOne.setPath("/blog/2023/02/22/jmeter-logging/");
httpSamplerOne.setMethod("GET");
httpSamplerOne.setName("HTTP Request One");
httpSamplerOne.setProtocol("https");
httpSamplerOne.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSamplerOne.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
This is going to make a request to one of our blog posts on JMeter logging.
We now create a Loop Controller:
// Create a loop controller
LoopController loopController = new LoopController();
loopController.setLoops(10);
loopController.setFirst(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
loopController.initialize();
We can see we have set the number of loops to be 10.
We now create a Thread Group:
// Create a thread group
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("First Thread Group");
threadGroup.setNumThreads(2);
threadGroup.setRampUp(1);
threadGroup.setSamplerController(loopController);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
We have set our threads to 2, ramp up to 1, and we have added our loop controller created above.
We now create a Test Plan:
// Create a test plan
TestPlan testPlan = new TestPlan("Test Plan");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());
We now add our Test Plan, which is the top-level element of our JMeter test to our hash tree:
// Add the test plan to our hash tree, this is the top level of our test
testPlanTree.add(testPlan);
We then add the Thread Group to the Test Plan, as this is our next level and allocate this to another hash tree:
// Create another hash tree and add the thread group to our test plan
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
And we add our HTTP Sampler to the Thread Group:
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOne);
Finally, we configure and run the test:
// Configure
jm.configure(testPlanTree);
// Run
jm.run();
Full class file for example one:
public class JMeterExampleOne {
public static void main(String[] args) throws Exception {
// Create the Jmeter engine
StandardJMeterEngine jm = new StandardJMeterEngine();
// Set some configuration
String jmeterHome = "C:\\Users\\Stephenje\\apache-jmeter-5.1.1\\apache-jmeter-5.4.1";
JMeterUtils.setJMeterHome(jmeterHome);
JMeterUtils.loadJMeterProperties(jmeterHome + "/bin/jmeter.properties");
JMeterUtils.initLocale();
// Create a new hash tree to hold our test elements
HashTree testPlanTree = new HashTree();
// Create a sampler
HTTPSampler httpSamplerOne = new HTTPSampler();
httpSamplerOne.setDomain("octoperf.com");
httpSamplerOne.setPort(443);
httpSamplerOne.setPath("/blog/2023/02/22/jmeter-logging/");
httpSamplerOne.setMethod("GET");
httpSamplerOne.setName("HTTP Request");
httpSamplerOne.setProtocol("https");
httpSamplerOne.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSamplerOne.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
// Create a loop controller
LoopController loopController = new LoopController();
loopController.setLoops(10);
loopController.setFirst(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
loopController.initialize();
// Create a thread group
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("Example Thread Group");
threadGroup.setNumThreads(2);
threadGroup.setRampUp(1);
threadGroup.setSamplerController(loopController);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
// Create a test plan
TestPlan testPlan = new TestPlan("Create JMeter Script From Java Code");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());
// Add the test plan to our hash tree, this is the top level of our test
testPlanTree.add(testPlan);
// Create another hash tree and add the thread group to our test plan
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOne);
// Configure
jm.configure(testPlanTree);
// Run
jm.run();
}
}
If we now run this test, we see that we get an exit code of 0 in the IntelliJ console window.
Great, we have just run a JMeter test written in Java.
Test Analysis and Results¶
On the surface it seems as though we have run a test, we did not get an error, so we assume it has worked as expected.
What we could do with is generating some output and viewing our test in the JMeter GUI to see if it is what we expect.
Let’s look at these now.
In our class we have already created let’s add this just above the final configure and run parts:
// Build a Jmeter test after execution
SaveService.saveTree(testPlanTree, new FileOutputStream(jmeterHome + "/bin/FirstTest.jmx"));
// Configure
jm.configure(testPlanTree);
// Run
jm.run();
The SaveService class will take our hash map we added our elements to and create a JMeter test that we can open in the GUI.
If we run our test again, we will see a .jmx file in our JMeter bin folder which we can open.
We can now see that our test looks as we expect it to.
However, what we really want to do is see our test running when we execute this from the code.
To do this we are going to add a Summariser to our code, below the SaveService code we have just included we add.
// Summariser
Summariser summariser = null;
String summariserName = JMeterUtils.getPropDefault("summarise.names", "summary response");
if (summariserName.length() > 0) {
summariser = new Summariser(summariserName);
}
ResultCollector logger = new ResultCollector(summariser);
testPlanTree.add(testPlanTree.getArray()[0], logger);
What we are doing is creating a Summariser to aggregate our results from our test, we add this to a ResultCollector and add the results collector to our hash map.
If we re-run our test:
We can now see that we are getting results from our test which gives us confidence that it is running.
Let’s now look at outputting these results to an output file.
We are going to add another ResultsCollector, this time we will configure it to output to a file in our JMeter bin folder.
// Write to a file
ResultCollector rc = new ResultCollector();
rc.setEnabled(true);
rc.setErrorLogging(false);
rc.isSampleWanted(true);
SampleSaveConfiguration ssc = new SampleSaveConfiguration();
ssc.setTime(true);
ssc.setAssertionResultsFailureMessage(true);
ssc.setThreadCounts(true);
rc.setSaveConfig(ssc);
rc.setFilename(jmeterHome + "/bin/FirstTest.jtl");
testPlanTree.add(testPlanTree.getArray()[0], rc);
This will output our results to a flat file called FirstTest.jtl
.
If we re-run our code, we can open the file generated:
We now have a simple test running with the ability to see the results being written to the console and to a flat file.
Additional elements¶
We are going to look at ways to add additional element to our test to make our current simple test more complex.
The first addition is a JSON Extractor which we add using this code:
// JSON Post Processor
JSONPostProcessor jsonExtractor = new JSONPostProcessor();
jsonExtractor.setName("JSON Extractor");
jsonExtractor.setRefNames("foo");
jsonExtractor.setJsonPathExpressions("$.title");
jsonExtractor.setProperty(TestElement.TEST_CLASS, JSONPostProcessor.class.getName());
jsonExtractor.setProperty(TestElement.GUI_CLASS, JSONPostProcessorGui.class.getName());
We will add this code to our project and for it to be added to our test we need to make a couple of changes to our hash tree.
As this is a post processor it needs to be added to our HTTP Sampler so to do this we add this code:
// Create a hash tree to add the post processor to
HashTree httpSamplerOneTree = new HashTree();
httpSamplerOneTree.add(httpSamplerOne, jsonExtractor);
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOneTree);
We are creating a hash tree and adding our JSON Extractor to our HTTP Sampler.
In our original test we had this line of code that added our HTTP Sampler to our Thread Group:
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOne);
We need to replace this with:
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOneTree);
Where we are now adding the hash tree to the Thread Group rather than just the HTTP Sampler.
If we re-run our code and look at the JMeter test that is produced:
We can see that the JSON Post Processor is a child of the HTTP Sampler as we would expect.
We are now going to add a Response Assertion.
We add this code to our project:
// Response Assertion
ResponseAssertion ra = new ResponseAssertion();
ra.setProperty(TestElement.GUI_CLASS, AssertionGui.class.getName());
ra.setName(JMeterUtils.getResString("assertion_title"));
ra.setTestFieldResponseCode();
ra.setToEqualsType();
ra.addTestString("200");
Where we are checking for a response code of 200.
To add this to our test we add it the same we did our JSON Post Processor as this needs to be a child of the HTTP Sampler.
So, we update our code adding this line below our JSON Post Processor:
// Create a hash tree to add the post processor to
HashTree httpSamplerOneTree = new HashTree();
httpSamplerOneTree.add(httpSamplerOne, jsonExtractor);
httpSamplerOneTree.add(httpSamplerOne, ra);
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOneTree);
If we now run our test:
We can see that our Response Assertion has been added as a child of the HTTP Sampler.
Finally, let’s create another HTTP Sampler and add this to our Thread Group.
Add this code to our project, it is identical to the first HTTP Sampler only a different URL:
// Create another sampler
HTTPSampler httpSamplerTwo = new HTTPSampler();
httpSamplerTwo.setDomain("octoperf.com");
httpSamplerTwo.setPort(443);
httpSamplerTwo.setPath("blog/2023/01/16/uncommon-performance-testing/");
httpSamplerTwo.setMethod("GET");
httpSamplerTwo.setName("HTTP Request");
httpSamplerTwo.setProtocol("https");
httpSamplerTwo.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSamplerTwo.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
This gets added to the test by adding this line of code that includes the second sampler name to the Thread Group hash tree under the first sampler tree:
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
threadGroupHashTree.add(httpSamplerOneTree);
threadGroupHashTree.add(httpSamplerTwo);
If we run our test again:
We can see that our second HTTP Sampler has been added under the Thread Group.
Conclusion¶
We have demonstrated how a JMeter test can be written in a Java project, this will prove useful when considering how you can build tests that run as part of the application code base to support agile development practises.
This is a very simple example but hopefully will give you a starting point to build on.
The full class file code is below:
import org.apache.jmeter.assertions.ResponseAssertion;
import org.apache.jmeter.assertions.gui.AssertionGui;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.gui.ArgumentsPanel;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.gui.LoopControlPanel;
import org.apache.jmeter.control.gui.TestPlanGui;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.extractor.json.jsonpath.gui.JSONPostProcessorGui;
import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui;
import org.apache.jmeter.protocol.http.sampler.HTTPSampler;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.reporters.Summariser;
import org.apache.jmeter.samplers.SampleSaveConfiguration;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jmeter.threads.gui.ThreadGroupGui;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;
import java.io.FileOutputStream;
public class JMeterExampleOne {
public static void main(String[] args) throws Exception {
// Create the Jmeter engine
StandardJMeterEngine jm = new StandardJMeterEngine();
// Set some configuration
String jmeterHome = "C:\\Users\\Stephenje\\apache-jmeter-5.1.1\\apache-jmeter-5.4.1";
JMeterUtils.setJMeterHome(jmeterHome);
JMeterUtils.loadJMeterProperties(jmeterHome + "/bin/jmeter.properties");
JMeterUtils.initLocale();
// Create a new hash tree to hold our test elements
HashTree testPlanTree = new HashTree();
// Create a sampler
HTTPSampler httpSamplerOne = new HTTPSampler();
httpSamplerOne.setDomain("octoperf.com");
httpSamplerOne.setPort(443);
httpSamplerOne.setPath("/blog/2023/02/22/jmeter-logging/");
httpSamplerOne.setMethod("GET");
httpSamplerOne.setName("HTTP Request One");
httpSamplerOne.setProtocol("https");
httpSamplerOne.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSamplerOne.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
// Create another sampler
HTTPSampler httpSamplerTwo = new HTTPSampler();
httpSamplerTwo.setDomain("octoperf.com");
httpSamplerTwo.setPort(443);
httpSamplerTwo.setPath("blog/2023/01/16/uncommon-performance-testing/");
httpSamplerTwo.setMethod("GET");
httpSamplerTwo.setName("HTTP Request");
httpSamplerTwo.setProtocol("https");
httpSamplerTwo.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSamplerTwo.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
// JSON Post Processor
JSONPostProcessor jsonExtractor = new JSONPostProcessor();
jsonExtractor.setName("JSON Extractor");
jsonExtractor.setRefNames("foo");
jsonExtractor.setJsonPathExpressions("$.title");
jsonExtractor.setProperty(TestElement.TEST_CLASS, JSONPostProcessor.class.getName());
jsonExtractor.setProperty(TestElement.GUI_CLASS, JSONPostProcessorGui.class.getName());
// Response Assertion
ResponseAssertion ra = new ResponseAssertion();
ra.setProperty(TestElement.GUI_CLASS, AssertionGui.class.getName());
ra.setName(JMeterUtils.getResString("assertion_title"));
ra.setTestFieldResponseCode();
ra.setToEqualsType();
ra.addTestString("200");
// Create a loop controller
LoopController loopController = new LoopController();
loopController.setLoops(10);
loopController.setFirst(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
loopController.initialize();
// Create a thread group
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("First Thread Group");
threadGroup.setNumThreads(2);
threadGroup.setRampUp(1);
threadGroup.setSamplerController(loopController);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
// Create a test plan
TestPlan testPlan = new TestPlan("Test Plan");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());
// Add the test plan to our hash tree, this is the top level of our test
testPlanTree.add(testPlan);
// Create another hash tree and add the thread group to our test plan
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
// Create a hash tree to add the post processor to
HashTree httpSamplerOneTree = new HashTree();
httpSamplerOneTree.add(httpSamplerOne, jsonExtractor);
httpSamplerOneTree.add(httpSamplerOne, ra);
// Add the http sampler to the hash tree that contains the thread group
threadGroupHashTree.add(httpSamplerOneTree);
threadGroupHashTree.add(httpSamplerTwo);
// Build a Jmeter test after execution
SaveService.saveTree(testPlanTree, new FileOutputStream(jmeterHome + "/bin/FirstTest.jmx"));
// Summariser
Summariser summariser = null;
String summariserName = JMeterUtils.getPropDefault("summarise.names", "summary response");
if (summariserName.length() > 0) {
summariser = new Summariser(summariserName);
}
ResultCollector logger = new ResultCollector(summariser);
testPlanTree.add(testPlanTree.getArray()[0], logger);
// Write to a file
ResultCollector rc = new ResultCollector();
rc.setEnabled(true);
rc.setErrorLogging(false);
rc.isSampleWanted(true);
SampleSaveConfiguration ssc = new SampleSaveConfiguration();
ssc.setTime(true);
ssc.setAssertionResultsFailureMessage(true);
ssc.setThreadCounts(true);
rc.setSaveConfig(ssc);
rc.setFilename(jmeterHome + "/bin/FirstTest.jtl");
testPlanTree.add(testPlanTree.getArray()[0], rc);
// Configure
jm.configure(testPlanTree);
// Run
jm.run();
}
}