API Load Testing: Enhance Your Skills with Locust
Tags: webdev, python, performance, testing
Published: 2024-02-28 13:30:00


Load testing is a serious job and doing it poorly can have disastrous consequences for big companies' production servers. But **load testing is not only for experts** in massive companies! You may want to quickly check that your server performances are appropriate, using only your own computer.

[Locust](https://locust.io/) is a perfect tool to use on such occasion:
- A great user interface, allowing you to **launch tests and monitor results in real-time**, directly from your web browser.
- Writting scripts in Python, perhaps the most popular language according to, for example, the [top programming language IEEE study](https://spectrum.ieee.org/the-top-programming-languages-2023).
- Installable in seconds, free and open source

## Start load testing with Locust!
First, you need an API to load test. Let's say you have an amusement park application. It's running on http://localhost:8080 and exposing the route `GET /bookings` with two query parameters: `age` and `date`.

To begin with Locust, you basically need a dozen seconds. With pip already installed, just run:
```shell
pip3 install locust
```

Then, you need to write a locust file, describing how users will interact with your API. Let's create a basic one, under the name `locustfile.py`:
```python
from locust import HttpUser, between, task

class User(HttpUser):

    wait_time = between(1, 2)

    @task
    def get_booking(self):
        age = 19
        date = "2024-06-08"
        self.client.get(f"/bookings?age={age}&date={date}")
```

The User class represents the expected behaviour of a user. For each user created, Locust will execute on a loop all the tasks defined. In this simple case, we have only one task and the requested URL will be always the same as age and date variables are hardcoded. We just set a pause of 1 to 2 seconds between each user loop.

Set-up is done, let's swarm into our API using Locust! In your terminal, **in the folder where your locustfile is located,** simply run:
```
locust
```
On your browser, head to http://0.0.0.0:8089/ and fill up the three parameters before launching the test:

![Locust interface filled with a number of users of 20, a spawn rate of 1 and host set to http://localhost:8080](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5nfgsi1dpcata05e4eja.png)

Press "Start swarming" and tadaa! ✨ We can now observe the response time of our API fluctuate in real-time:

![A time serie graph representing the 50th and 95th percentiles of response time](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/91ibhfebbqlkngf65ftu.png)

As the number of users grows:


![A time serie graph, showing the number of active users ramping up until 20 before stabilizing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arnrqmks9zby8ru3v166.png)

Once the test is done, you can download a report in HTML format. Allowing you to quickly share the results visually with your team.

## Benefiting from Locust plugins to deliver more value
Here, we load-tested our API in a basic way (one endpoint, always the same request parameters). We can do a lot more, and most of the time we must!

Let's say your API is enjoying some success. To handle the increasing load, you [implement some cache on your endpoint](https://blog.theodo.com/2023/09/how-to-cache-in-java-application/), allowing fast response to common requests. It's nice, but **if you keep having only one request when load testing, cache will handle all your requests.** So your server will be able to accept way more requests than in a real situation, where all requests could be different and cache would miss some of them.

To solve this issue, we should **vary the parameters of the requests we send**, that is `age` and `date`. Here is one of the many possibilities, using the [CSV reader plugin for Locust](https://github.com/SvenskaSpel/locust-plugins/blob/master/examples/csvreader_ex.py):

First, we need to create a CSV file listing all the test cases we want to use, for example in a file named `booking-param.csv`:
```
12, 2024-06-08
20, 2024-06-08
12, 2024-06-09
20, 2024-06-09
```
You can use a little script to build this CSV by making the [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product#A_deck_of_cards) of all your parameters' possible values.

Then, we modify the locust file to import our parameters from the CSV file:
```python
from locust import HttpUser, between, task
from locust_plugins.csvreader import CSVReader # add import to the CSV reader plugin

booking_param_reader = CSVReader("booking-param.csv") # read the CSV you created

class User(HttpUser):

    wait_time = between(1, 2)

    @task
    def get_booking(self):
        booking_params = next(booking_param_reader)
        age = booking_params[0]
        date = booking_params[1]
        self.client.get(f"/bookings?age={age}&date={date}", name="/bookings")
```
Relaunch your script and each task will be executed with a different line of the CSV file you created, making each request different (if you send more requests than there are lines in your CSV file - it will quickly happen - further requests will be sent starting over from the start of the file).

Note that here we add a `name` argument to our `get` method: **Locust will group all these requests in one line in its report,** which would prove very convenient for analysis!

## Stay tuned: Locust is evolving
In the [2020 exhaustive comparison of load testing software](https://k6.io/blog/comparing-best-open-source-load-testing-tools/) (done by a k6 engineer 😉), Locust was pinpointed for its low performance and the clumsiness of running it locally on several CPU cores. Since then **Locust can run a process for each of your cores with a single command:**
```
locust --processes -1
```
Although Locust was created in 2011, it keeps gaining functionality, making it worth giving it a (re)-try!