Taurus: Writing JMeter Load Tests As Code
JMeter is one of the oldest and widely used tool for load testing applications. However it’s GUI based, and JMX the storage format it uses for test plans is not user friendly (example file). This makes it hard to collaborate with other team members.
Taurus is an open source tool that provides a friendly abstraction over JMeter. It allows one to write the test plans in a lightweight YAML format which are easier to read compared to JMX.
Installation
Follow the installation steps from Taurus here
Simple test plan
Let’s come up with a very simple test plan to familiarize ourselves with Taurus.
scenarios:
home:
requests:
- url: http://localhost:8000/home
execution:
- scenario: home
concurrency: 1
iterations: 10
This will make a HTTP GET request to the endpoint http://localhost:8000/home
for 10 times.
Save the above as main.yaml
and run using bzt main.yaml
. For learning purposes, you can create a dummy server by running python3 -m http.server
and create dummy “endpoints” using touch home account
In real world applications, we’ll obviously make more than one request. We can specify sequential requests as follows:
scenarios:
home:
requests:
- url: http://localhost:8000/home
+ - url: http://localhost:8000/account
execution:
- scenario: home
concurrency: 1
iterations: 10
Let’s also add a POST request:
scenarios:
home:
requests:
- url: http://localhost:8000/home
- url: http://localhost:8000/account
+ - url: http://localhost:8000/login
+ method: POST
+ body: '{ username:"user", password:"hunter2" }'
execution:
- scenario: home
concurrency: 1
iterations: 10
Then add some headers and cookies:
scenarios:
home:
+ headers:
+ Cookie: 'connect.sid=abc'
+ authorization: Bearer 123
requests:
- url: http://localhost:8000/home
- url: http://localhost:8000/account
- url: http://localhost:8000/login
method: POST
body: '{ username:"user", password:"hunter2" }'
execution:
- scenario: home
concurrency: 1
iterations: 10
More examples here.
Parameterizing values
We can pass values from command line to the test plan using “JMeter properties”. Say we want to configure the iterations
from command line, we can run the test using:
bzt main.yaml -o modules.jmeter.properties.iterations=20
and change our script to use this parameter:
scenarios:
home:
requests:
- url: http://localhost:8000/home
- url: http://localhost:8000/account
execution:
- scenario: home
concurrency: 1
- iterations: 10
+ iterations: ${__P(iterations)}
Multiple parameters can be passed this way. More examples and configuration options here.
Splitting scenarios into multiple files
As your application becomes more complex, your scenarios would also become longer. At some point, you’d need to split your scenarios into multiple files. Let’s move the home
scenario to a new file home.yaml
and import it into main.yaml
+ included-configs:
+ - home.yaml
- scenarios:
- home:
- requests:
- - url: http://localhost:8000/home
- - url: http://localhost:8000/account
execution:
- scenario: home
concurrency: 1
iterations: 10
# home.yaml
scenarios:
home:
requests:
- url: http://localhost:8000/home
- url: http://localhost:8000/account
Using data from CSV file
Sometimes you would need to test your endpoints with multiple inputs. The data sources
feature comes in handy those times. You can save your inputs as a CSV file and refer them in test file.
# home.yaml
scenarios:
home:
+ data-sources:
+ - path: customers.csv
+ random-order: false
requests:
- url: http://localhost:8000/home
- - url: http://localhost:8000/account
+ - url: http://localhost:8000/account?customer_id=${customer_id}&customer_name=${customer_name}
Contents of customers.csv
:
customer_id,customer_name
1,John Doe
2,Jane Smith
3,Michael Johnson
As you can see above, the columns in the CSV file get converted to variables. These variables are initialized with values from one row of the file for each request and can be used in urls, headers etc. More options here.
Using scripts for processing requests
Sometimes you need to pass the output of an endpoint into the input of a subsequent endpoint. This can’t be done using YAML, so we need to rely on scripting languages like JavaScript, Groovy etc.
# home.yaml
scenarios:
home:
requests:
- url: http://localhost:8000/home
- url: http://localhost:8000/account
- url: http://localhost:8000/user
jsr223:
- language: javascript
execute: after
script-text: |
var response = JSON.parse(prev.getResponseDataAsString());
var userId = response.userId;
print(userId);
vars.put("user-id", userId);
- url: http://localhost:8000/log
method: POST
headers:
Content-Type: application/json
jsr223:
- language: javascript
execute: before
script-text: |
var requestBody = {
"userId": vars.get("user-id")
};
vars.put("request-body", JSON.stringify(requestBody));
body: ${request-body}
In this example, we add a script that executes after the /user
request to extract the userId
and use it before the /log
request to construct a payload.
Notice how I used print()
instead of console.log()
for debugging. This is because the JavaScript used in JMeter is different from the ones used in Web Browsers or NodeJS, so some keywords or APIs would not work. Also, JMeter developers recommend using Groovy instead of JavaScript if you’re doing heavy load tests.