In this Video Tutorial, we will Understand the Basic Building Blocks of a Gatling Script and will also learn to Create a Galing Script Project in Maven/Gradle from scratch:
Gatling building blocks typically include feeders, load profiles, and assertions. Together these components constitute a Gatling simulation and help in writing reusable pieces of code as building blocks of single or multiple simulations.
We will also go through the different elements that get created as a part of the Maven archetype and the problem each one of them solves.
=> Check All Gatling Tutorials Here
Table of Contents:
Gatling Script Concepts
Few major common elements are part of almost all the scripts and can essentially be considered to be the building blocks of Gatling script.
Before dwelling further, let’s first discuss the scenario that we will be used for simulation. To keep it simple, we will use create user endpoint from REQ|RES (which has a lot of dummy endpoints for testing purposes)
Endpoint – https://reqres.in/api/users
Method – POST
Request payload:
{ "name": "bob", "job": "painter" }
The code snippet below explains how to create a simulation class with different components:
// basic Gatling imports import io.Gatling.core.Predef._ import io.Gatling.http.Predef._ // 1. every scenario class should extend from Simulation class class createUser extends Simulation{ // 2. define protocol val httpProtocol = http .baseUrl("https://reqres.in") .acceptHeader("application/json") // 3. define scenario def createUsers() = { exec(http("create a user") .post("/api/users") .body(StringBody( """ |{"name":"bob","job":"painter"} """.stripMargin)).asJson) } val scn = scenario("create list of users") .exec(createUsers()) // 4. setup scenario with load profile for execution setUp(scn.inject(atOnceUsers(1)) .protocols(httpProtocol)) }
Let’s understand the different sections represented in the above code snippet:
#1) All the simulation scripts should extend from Scala class – Simulation (this contains all basic building blocks/libraries to be used within the simulation script).
#2) Now, we need to define the protocol which is used by the simulation class. For example, in this case – we are using Http protocol with base URL as “https://reqres.in” and a common header “Content-Type: application/json”
#3) Next comes the scenario definition. As our scenario is to create a user using endpoint “/api/users”, in this section, we need to create or define everything about our scenario. For example, the request method (HTTP POST), request body, etc.
Please note, if there were multiple requests as part of the simulation, we would have defined those as part of this section itself.
#4) This is the final step, where we instruct Gatling to set up the script with the given scenario and also mention the load profile with which we want to run the test (For example, here we have mentioned atOnceUsers(1) which means we want to run the simulation with 1 user.)
We will look at the load profiles in detail in the next sections of this tutorial.
Gatling Script Core Elements
Now, let’s discuss the Gatling script core elements one by one:
#1) Configuring load/injecting users
For any load testing script, load configuration is one of the most essential component and Gatling provides an easy and super convenient approach to inject load configuration for the simulation to be executed.
In the world of Gatling, this is termed as Injection Profile i.e. the setup to control the user injection in a script.
Please refer here for official documentation in Gatling.
Commands available to configure load in a Gatling simulation script includes:
(i) atOnceUsers(no of users): The command atOnceUsers is used to inject/add a given number of users when this command is called. Look at it like, you want to start the test with say 5 users or assume that every morning there are 5 users on your web application. This command atOnceUsers(5) will inject 5 users at the same time to the test.
(ii) rampUsers(no of users)during(duration): This is used to generate a linear load over a given time. For example, If you want to generate a load of 100 users over a duration of 60seconds, you can use this command like:
scn.inject(rampUsers(100).during(60 seconds))
(iii) constantUsersPerSecond(no of users)during(duration) This command is used to inject a constant number of users per second for a given duration.
This is analogous to simulating linear traffic of say 10 req/second over a period of 1 minute. The command can be written as:
scn.inject(constantUsersPerSec(5).during(10 seconds))
(iv) nothingFor(duration): This is simply a kind of pause in the injection profile – i.e. you want to inject no new load for a given period of time. You can use this command like:
scn.inject(nothingFor(10 seconds))
Please note, that this command has no meaning when used in isolation. It is mostly used while designing complex load scenarios or profiles with a no-load period in between.
(v) incrementUsersPerSec: This command is used to design complex load patterns. It’s kind of shorthand for specifying repeat profiles with a mix of linear load and ramp-up.
Sample load profile:
scn.inject( incrementUsersPerSec(2) .times(3) .eachLevelLasting(3 seconds) .startingFrom(0) .separatedByRampsLasting(3 seconds)
(vi) constantConcurrentUsers(no of users): This command allows us to add concurrent users to the simulation. All the previous load injection we have discussed had virtual users executing the request once. But the concurrent users will simply continue to execute the request as soon as they finish an earlier one.
You can use this command like:
scn.inject(constantConcurrentUsers(2)during(10 seconds))
An important point to note here about the feeders is, these can be used in combination for designing different kinds of load profiles.
For example – if you want to create a load profile like
- No users for the first 10 seconds.
- Ramp up 10 users in the next 10 seconds.
- Keep adding 5 users per second for the next 10 seconds.
We can combine these load configuration commands to achieve the above-mentioned load profile as shown below:
setUp(scn.inject( nothingFor(10 seconds), rampUsers(10) during(10 seconds), constantUsersPerSec(5) during(10 seconds)) .protocols(httpProtocol))
#2) Feeders
Feeders are data sources for the scenario or scripts that are being executed. Running a performance or load test on an API endpoint, a lot of times require, different or configurable data values to be used for different injected users.
For example, consider an endpoint that performs the addition of two input variables. We want to test it for different values. Suppose we are executing this endpoint for 100 concurrent users, we would like to test it with different inputs, so as to distribute the input sets and avoid hitting or getting cached results back from the server.
The 2 important types of feeders that are most widely used are csvfeeder and jsonfeeder
CSV feeder as the name implies provides the data source through a CSV file. At each iteration, the row of the CSV file is supplied as a map to the simulation and hence the values could be used as simple variables.
Let’s see this in action with the below steps:
(i) We will use the same application REQ|RES and will use the endpoint to create a user that expects a JSON payload having fields – name and job.
(ii) Store 3-4 values of name and job in the CSV file as shown below.
Save this data as CSV file – with name samplecsvdata.csv and store it in the data folder under resources. The directory structure would look like – test/resources/data/samplecsvdata.csv
name, job fred, guitarist bob, teacher amit, computer engineer shekhar, professor
(iii) Now let’s come to the actual simulation – to define the CSV feeder, simply use the CSV Gatling DSL and supply the relative location of the CSV file in the resources folder
val csvFeeder = csv("data/samplecsvdata.csv")
(iv) While defining the request, we can substitute the values from the feeder data, by using the feeder declared above – Refer to the code snippet below:
// declare feeder val csvFeeder = csv("data/samplecsvdata.csv") // define simulation request def createUsers() = { feed(csvFeeder) .exec(http("create a user") .post("/api/users") .headers(header_map) .body(StringBody( """ |{"name":"${name}","job":"${job}"} """.stripMargin)).asJson)
Here we are using the declared feeder in request declaration – and substituting the values from the feeder in the request body – refer to the usage of ${name} and ${job} fields in the request body.
Similar to CSV feeder, there’s another feeder called JSON feeder. Here the data is stored as JSON blobs and should be part of a JSON array in the JSON file –
Here we have referred to the same data (as we did in CSV), to be defined as JSON array
[ { "name": "fred", "job": " guitarist" }, { "name": "bob", "job": " teacher" }, { "name": "amit", "job": " computer engineer" }, { "name": "shekhar", "job": " professor" } ]
Simply save this file as JSON in resources directory, and declare JSON feeder similar to the CSV feeder just replacing the CSV command with JSON command as below:
val jsonFeeder = jsonFile("data/sampleJsonData.json")
The usage of the JSON feeder remains the same as CSV. During the run time the feeder injects each element of JSON array as a key, value map with keys being the JSON property names and values being the property values respectively.
It’s also important to discuss another concept w.r.t. feeders that are called Feeder Strategy.
By default, the feeder strategy is a queue. It is the way we want to use the feeder data. An important point to note here is if there are more iterations of the simulations then the data available in the feeder, then the simulation will throw an error and exit. However, there are certain caveats to this, depending on the feeder strategy that’s chosen.
In total there are 4 different feeder strategies available in Gatling. These are aa follows:
- Queue: This is the default strategy that gets applied to the feeder and is as simple as any first in the first out queue. Look at it like there are 100 plates available and there’s a queue of quests – each guest gets one plate and queue keeps on reducing in size.
- Circular: In this strategy, the feeder repeats the elements from the start once the elements in the feeder get over.
For example – in a queue of guests, suppose they are waiting for their turn to get a sweet. Once a person gets sweet, he eats it and goes at the back of the queue to get the sweet again. - Shuffle: Shuffle is similar to queue, the only difference being the values supplied or popped out is not in a sequence but in a shuffled manner.
- Random: This chooses a random value every time a feeder is required to supply a value – Same value could be chosen multiple times in this strategy. Also, no of iterations can be more than the number of items available in feeder using this strategy.
To use any of these feeder strategies, simply mention these when the feeder is declared. For example, to use a random strategy on a CSV feeder, you could use the below command
val csvFeeder = csv("data/samplecsvdata.csv").random
Here is a Video Tutorial:
#3) Assertions
Gatling Assertions are commands or code snippets used to validate an expected outcome for a load test that’s executed through the injected simulation. Gatling assertions are applied to the simulation setup as a chained method/call.
An assertion has typically four components –
- Scope of assertion: Use global to apply at the entire simulation level. This applies to statistics calculated from all the requests, and “forAll” for each request.
- Statistic: This is the actual parameter that you want to assert on – for example, Gatling provides several statistics like – failedRequests, responseTime, requestsPerSec, etc. as predefined statistics.
- Metric: Metric is the measure that a user wants to assert to – for example for responseTime related statistics the metrics that are available are – min, max, mean, stdDev, percentile and for the number of requests statistics like failedRequests – the available metrics are – percent and count.
Please note, that the threshold value of duration is always in milliseconds. - Condition: Condition is the final component of any assertion and its the actual comparison operator that’s to be applied on the selected statistic and metric – for example, lt(thresholdValue) – is used to compare the given statistic and metric combination and expect it to be less than some given threshold value. Similarly – gt, lte, gte, between, and eq are other operators that can be used as conditions.
So, let’s see how an assertion looks like combining all the 4 components that are discussed above. We are writing a global assertion to validate maximum response time during the entire simulation run is less than 300 ms.
setUp(scn.inject(atOnceUsers(3)) .protocols(httpProtocol)) .assertions( global.responseTime.max.lt(300) )
We can also specify multiple assertions as part of the assertion block. Suppose, we want to add another assertion which checks that there are no more than 2 failed requests during the entire simulation run.
Please refer to the code snippet below.
setUp(scn.inject(atOnceUsers(3)) .protocols(httpProtocol)) .assertions( global.responseTime.max.lt(300), global.failedRequests.count.lte(3) )
#4) Checks
Checks are similar to assertions, but instead of asserting on a response metric, these are assertions on the actual response body or headers, etc. Please note that the checks can be added while defining a scenario – i.e. while defining the scenario request. For example, you can add checks for scenarios like
a) Validation on the Http status code for the response received:
val scn = scenario("Create User") .exec(http("create a user") .post("/api/users") .body(StringBody( """ |{"name":"fred","job":"software tester"} """.stripMargin)).asJson .check(status.is(201)))
b) Check for the actual response body containing a particular string:
val scn2 = scenario("Find a user") .exec(http("find user with given id") .get("/api/users/2") .check(substring("email").exists))
c) Checking for presence of a particular header
val scn2 = scenario("Find a user") .exec(http("find user with given id") .get("/api/users/2") .check(header("Content-Type").exists))
Here is a Video Tutorial:
Tips And Tricks
- An assertion failure marks the current request as failed (i.e. the request for which assertion was applied to).
- Assertion success or failure is captured in the HTML report generated at the end of the test run.
- They are validations on response metrics while checks are validations on the actual response received, for example, response body, URL and headers.
- Checks can be mentioned or applied to different requests of the same scenario.
Creating Gatling Maven Project
Gatling Maven project can be created by directly importing the required Maven dependencies in a Maven project pom.xml file, or we can create the project using Maven archetype for Gatling which comes with all the setup and libraries that are required to get started with a Maven-based Gatling project.
For those who are not familiar with the Maven archetype concept – its nothing but a kind of template to seed a project with some predefined dependencies and classes. In other words, it’s a bootstrap to get started quickly and avoid writing boilerplate code every time when a project is created.
To understand this in detail, please refer to Maven’s article on the archetype.
In order to get started with Maven archetype, follow the below steps:
#1) Install Maven: Skip this step if you have already installed Maven. It can directly be installed using the downloadable zip file from Maven’s official website.
(For Mac users, same could be installed using brew – just run the command brew to install Maven).
#2) Once Maven is installed, run the command “mvn -v” to get the Maven and Java version details. If the command works correctly then that means that the Maven is successfully setup.
#3) Now run the command to generate a list of Maven archetypes.
mvn archetype:generate
#4) When prompted for a name or filter type in “Gatling” to search for Gatling related archetypes.
#5) Now select the filtered result as the archetype to create the project with. Add the other details like groupId, artifactId, etc. for the project that you want to create. Also, add some sample values for these fields as shown below.
#6) The Maven project with selected archetypes should get created. Navigate to the directory where the package is created and open it in IDE (Like Eclipse or Intellij). The folder structure of the project will look like as shown below.
Note: In order to ensure the execution of this project, please add the Scala plugin and library as well to the interface. In order to setup Scala in Intellij community edition, please follow the steps here
Maven Project Structure
Let’s take some time here to understand the project structure that is created when we have used the Maven archetype to create our Gatling project.
#1) Engine And Recorder Class
Engine class is a program to execute the Gatling executables, which looks for the simulation scripts in the project’s test directory and lists down them for execution.
If there are multiple scripts that are part of the same project, then executing this class will prompt an option to choose the simulation that the user wants to run.
Recorder Class is another utility in order to open the Gatling script recorder with pre-filled information for the class and package details as well as the simulation directory being the current project location of the recorder class itself. It’s a useful utility to start the recorder directly, instead of running the shell script manually every time.
#2) Resources folder
Configuration files: There are a couple of configuration files, that come along with the Maven archetype. Let’s understand all of them one by one
a) Gatling.conf – This configuration file has all the details/settings related to the Gatling script runner as well as lots of configuration options for the final report generated.
Discussing every property in detail is beyond the scope of this article, but with a lot of documentation available inside the file, most of the properties are self-explanatory.
For Example – let’s look at configuration options available under “charting” section in the file.
charting { #noReports = false # When set to true, don't generate HTML reports #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration. indicators { #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite } }
Here, we can change things like – percentile values – for example, if you don’t want 75 and want 80 percentile values, you can go ahead and change here (and when scripts are executed using the Gatling executor, these properties will be applied)
b) Logback.xml – Logback is a pretty standard logging configuration file and is used to related log related settings that display on the console while running the simulation.
c) Recorder.conf – These are settings or configuration options for the Gatling recorder to be launched. Things like blacklist templates, white lists, the proxy configuration could be specified in this file and they get applied when Gatling recorder is launched through the Recorder class that’s generated as part of the project.
Running The First Script
We will use the same script that we have created in the last article using the Gatling recorder. Let’s copy that script class in the created Maven project and execute using the Engine class or through the command line.
Please follow the below steps:
#1) Add a simulation script in test.Scala package folder.
#2) Now to execute the added stimulation, it could be done using any of the 2 approaches below
a) Type in mvn gatling:test, this will look for the available simulations in the test package/folder and execute them. An important point to note here is that if there are multiple simulations available in the project/package, then it should be specified before running the command, else it will throw an error.
So, in that case, the command should be used like:
mvn Gatling:test -DGatling.simulationClass=fullClassName
Here, the full class name is the fully qualified name of class along with the package – So, if the package name is com.learn.Gatling and the Class name is MyFirstProject, then we will need to specify the class name in above command as com.learn.GatlingMyFirstProject.
b) The second way of executing the Gatling tests in the project is using or executing the “Engine” class that comes as part of the archetype or template itself. It is a Gatling provided script runner.
The good part about this class is – if there are multiple scripts or simulations that are part of the same project, unlike throwing an error, it will show a prompt to choose which simulation the user wants to execute.
A sample screenshot below
c) When the execution is complete, all the reports/result files go-to target/Gatling folder
(If this location needs to be updated/modified, it could be done in the IDEPathHelper class – resultsDirectory property)
Here is a Video Tutorial:
Other Useful Gatling DSL Commands
Let’s look at a couple of commands which are used to introduce kind of wait or control during the Simulation execution. Gatling DSL has a lot of commands out of which we will discuss some commonly used ones – Pause, pace, and throttle.
#1) Pause
Pauses are used to simulate user’s think time. For example, If you are shopping on an eCommerce website. The journey of selecting a product and adding it to the cart to checkout is as follows:
- First, you will go to the homepage of the app like Amazon.com.
- Wait for the page to load and then search for the product that you are looking for.
- Then you will look at the product’s price, read reviews, if available and add the product to cart.
- Finally, you will review the cart and complete the payment in order to complete the purchase.
Now in the above 4 steps, there are few implicit things about the user’s behavior – the user will wait every time till the page loads as well as while taking the decision to do the next step in the purchase. This also implies that some users think about time.
Hence, while designing a user scenario to be load tested, think the time is an important parameter and should be as close as what it would be when a real user executes that scenario.
In order to add a pause between any 2 requests between scenarios, simply use the pause() command. In Gatling, pauses can be fixed length or random length (and can be achieved through different overloads available for the pause() method).
- Fixed length pause – pause(Duration object)
- Random length pause – pause(min Duration, max Duration)
Let’s see a sample script using the pause command between 2 requests in the scenario description.
Suppose our scenario consists of 2 requests – creating a user and fetching a user with the given id. And we want to add a pause of 500 ms in between these requests, then we could use it as per the below code snippet.
val httpProtocol = http .baseUrl("https://reqres.in") .acceptHeader("application/json") // define 1st request def createUsers() = { exec(http("create a user") .post("/api/users") .body(StringBody( """ |{"name":"bob","job":"painter"} """.stripMargin)).asJson) } // define 2nd request def findUser = exec(http("find user with given id") .get("/api/users/2") .check(header("Content-Type").exists)) // setup scenario val scn = scenario("create user and then find user with id 2") .exec(createUsers()) .pause(500 milliseconds) .exec(findUser) // execute scenario setUp(scn.inject(atOnceUsers(5)) .protocols(httpProtocol))
#2) Throttle
The throttle is used to ignore requests from the system if it breaches an expected threshold value. It is generally used when it’s desired to maintain a constant throughput of requests per second that are being sent to simulation under test.
For example – if there are 100 users, and you want to limit the throughput for your application to 10 requests per second, then even if there can be more load generated with the given set of users, the throttle command will limit the rate of requests depending on the Rps(Requests Per Second) set by the user.
Refer the code snippet below:
// setup scenario val scnGet = scenario("find user with id2") .exec(findUser) // execute scenario setUp(scnGet.inject(constantUsersPerSec(100)during(30 minutes)) .throttle( reachRps(10) in (10 seconds), holdFor(1 minute), jumpToRps(20), holdFor(2 minutes), jumpToRps(30), holdFor(3 minutes) ) .protocols(httpProtocol))
Here, we’ve set up a scenario to get a user with id = 2 and used the throttle to reach a target Rps and hold the required Rps for a given duration.
Please note that in order to achieve the requested throughput, there must be enough concurrent users available to achieve that else the users will be over-utilized and target throughput might not be reached.
#3) Pace
A pace is a dedicated form of pause. In simple words, it is used in scenarios where you want to control how frequently an action is executed. An important point to note here is that the pace adjusts itself depending on the time taken by the chained action to execute.
Let’s see this in action with the help of below code snippet:
// setup scenario val scnGet = scenario("find user with id2") .pace(5 seconds) .exec( pause(2 seconds), findUser ) setUp(scnGet.inject(constantConcurrentUsers(2)during(1 minute)) .protocols(httpProtocol)) .maxDuration(30 seconds)
Here, using pace will ensure that the pace of 5 seconds will try to maintain a gap of 5 seconds in consecutive executions. And it will adjust its time depending on the duration of actual pause as well as the time taken for the request to execute.
Conclusion
In this tutorial, we have covered creating Gatling Project using a Maven archetype as well as walked through different building blocks of a Gatling simulation script.
We have talked about components like feeders, assertions, checks which are the components to build any simulation in Gatling.
Gatling DSL documentation page is the official source of all the commands that are supported by Gatling and is greatly a means to write effective simulation scripts with a lot of flexibility and reusability.
Gatling does provide DSL and capability to performance protocols other than Http – for example, you can create scripts to test web socket performance using Gatling, MQTT, JDBC queries, etc.