menu

Gatling: Take Your Performance Tests to the next Level

Gatling is a powerful open-source Performance Test tool released in December, 2011. It was also mentioned in the ThoughtWorks Radar 2013 and 2014 as a tool worth trying. Gatling is a lightweight DSL written in Scala that comes with the interesting premise of "treating your performance tests as production code".

An attractive point about Gatling is that you can define and write your performance test scenarios in the same way as you are used to doing it with other test automation frameworks. You can thus develop readable and easy to maintain performance test code that can be handled by your version control system as production code.

Gatling integrates easily with Jenkins through the jenkins-plugin, and can also be integrated with other continuous delivery tools. This is great because you can obtain constant feedback from your performance tests as you develop your application. Another plus is that you can easily run your tests through Maven and Gradle with the help of the maven-plugin and gradle-plugin. Gatling also provides elegant and meaningful reports that are easy to analyze and understand what is going on with your application.

The latest stable version (1.5.5) supports Linux, OSX and Windows. Gatling 2 is currently in development and is coming with more interesting and powerful features.

So now that we know a little more about Gatling, how about we take a deeper look on it?

Basically, Gatling structure can be defined in 4 different parts:

  1. HTTP protocol configuration – This will define your base URL that you will be running your tests against. Also, you can define some other configurations such as user agent, language header, connection and so on.
  2. Headers definition – This provides the headers for each request that will be sent to the server. This is relevant because the headers also add a bit of load to the server you are testing.
  3. Scenario definition - The core of your test! A Scenario is a group of actions (GET, POST, etc.) that will be executed in order to simulate a user interaction with your application.
  4. Simulation definition - This defines the load (amount of users) that will concurrently execute your scenario for a period of time.

Ok...talk is easy, show me the code!

Let's imagine a simple web application where users can upload text files with some data. The app provides a button that the user can hit to verify if the data uploaded matches with any data in the system.

Firstly, we will add some basic imports and start defining our HTTP Configuration. We set our baseURL to http:://my-application:8080. This means that every time we run a request, it will use our baseURL following by the other configurations such as acceptHeader, acceptEncodingHeader, acceptLanguageHeader, connection and userAgentHeader.

package com.myapplication.app
import com.excilys.ebi.gatling.core.Predef._
import com.excilys.ebi.gatling.http.Predef._
import assertions._
class UploadFileScenario extends Simulation {
  val httpConf = httpConfig
    .baseURL("http://my-application:8080")
    .acceptHeader("image/png,image/*;q=0.8,*/*;q=0.5")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("en-US,en;q=0.5")
    .connection("keep-alive")
    .userAgentHeader("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36")

After that, we will define a scenario called "Upload a file". This scenario will execute a group of actions. The first action will be a GET to the homepage. The second one will be a POST with an attachment of a .txt file. Note that we are using two interesting commands here: check() and saveAs(). Basically what they do is this - after we POST our data, we use check() to verify the response of it. Considering we need a fileId for the next request, we are parsing the fileId (through a Regular Expression) and then using saveAs() to store this data in a variable called fileId.

    val scn = scenario("Upload a file")
    .exec(http("homepage_GET")
        .get("/my-application/app/service/workgroup/latest")
        .header("Content-Type", "application/json")
    )
    .exec(http("attach_txt_file_POST")
        .post("/my-application/app/service/file/upload/")
        .header("Accept", "text/html")
        .upload("myFile", "file.txt", "text/plain")
        .check(regex("<div id=\'fileId\'>(.*?)<\/div>")
        .saveAs("fileId"))
    )

Our third action is another POST. As you can see below, we are now passing our fileId as a parameter of our body. Also, we use check() again to verify our response and parse it through JSONPath and then using saveAs() to save workgroupId for our next step.

    .exec(http("upload_txt_file_POST")
        .post("/my-application/app/service/workgroup/")
        .body( """{"name": "my-perf-test-${fileId}", "fileId": "${fileId}"}""").asJSON
        .check(jsonPath("workgroupId")
        .saveAs("workgroupId"))
    )

In the last action of our flow, we are doing one more GET and passing our previous captured workgroupId as a parameter.

.exec(http("run_content_match_GET")
.get("/my-application/app/service/workgroup/contentmatch/${workgroupId}")
.header("Content-Type", "application/json")

)

Ok, now we have our flow defined: we access our homepage, we attach a .txt file, we upload it and then run another action called content match. So, what do we want to test here? That's what we need to tell Gatling through setUp() and assertThat(). In our setUp() we are telling Gatling: "Hey, please simulate my whole scenario for 10 users in 30 seconds". It basically means that our "users" will start interacting with our application progressively. In this case, after 3 seconds a new user will start doing our flow.

setUp(
    scn.users(10).ramp(30).protocolConfig(httpConf)
)

Finally, we need to define our expected results. Gatling provides a good amount of assertions to help us with that. In this case we are asserting that none of our requests will fail. We could say for instance: "I want to make sure all my requests won't take more than X seconds".

assertThat(
    global.failedRequests.count.is(0)
)

If you would like to see the full code example above, you can take a look here.

It's nice that Gatling can be used not just for simulating user load, but also to check HTTP Responses. You can use Gatling Assertions to verify if your requests returned a 404 code or an expected result such as a specific JSON value, for instance. Gatling allows you to use Regular Expressions, CSS Selectors, XPath and JSONPath to parse your response and extract the information you are interested in and even use it further in your next steps! For more information about Gatling DSL, I recommend you take a look at this awesome cheat sheet.

If you are curious to see how a Report looks like, here is an example (of course, there are many other views):

It is worth to mention that Chrome Dev Tools come in handy to help you identifying GETs, POSTs or whatever it is that you want to simulate. The "Network tab" is extremely helpful in this case. I would also recommend you to use cURL as a support tool. It can help you to simulate easily your requests before you map them to inside your Gatling test scenarios.

Bonus tip: In Chrome Dev Tools (in the Network tab) you can right click on the request you are interested and select the option "Copy as cURL" ;)

Gatling also provides you a GUI, but you definitely do not need it due to the simplicity of its DSL.

So go on, start treating your performance tests as production code!