We carry out load testing

pip3 install locust

Now we need to create a script that will actually be executed by Locust. To do this, in a separate directory, create a file locustfile.py with the following content:

from locust import HttpUser, task
            
class User(HttpUser):
    @task
    def mainPage(self):
        self.client.get("/")

As you can see, here we will simply make a GET request. If you want to use a different name for the file, you will need to add the -f option and the file name when executing. To run this test, we will need to run the locust command in the script directory from the command line, which will launch the web user interface on port 8089:

$ locust

If the launch is successful, the web interface on port 8089 becomes available to us, with which we can run tests. If necessary, you can change the port using the command locust –web-port [порт].

Here we can manually run a simple load test by specifying the number of users and the time within which these users should connect.

As a test subject, I deployed a container with Nginx on port 80. Those who want to deploy a similar “stand” can do this using the command:

docker run --name nginx -d –rm -p 80:80 nginx

You can now specify 1000 users, 10 seconds, and http://web_resource_address. Click Start and enjoy the results in different tabs.

Of course, when Failures =0 this is somehow not entirely correct, but we just have the default Nginx page. So, everything works, but I would like to somehow complicate the tests.

First, let's run the tests using the command line.

locust --headless -u 1000 -r 10 -H http://адрес

The first parameter is the number of users, the second is the time interval and then the address itself.

Full automation

Let's complicate our testing. Now we will use a special resource as a target https://www.demoblaze.com. This is like an online electronics store designed specifically for the tasks of testers.

Before writing tests, let's look at typical user behavior on an online store website. First, you need to go directly to the site, then you need to log in in order for the discounts for regular customers to apply. Users who visit this resource for the first time will naturally not log in, but will immediately proceed to adding items to the cart, but we will consider the most resource-intensive option, when authorization occurs first.

Next, the user adds the product to the cart and actually goes to the cart for checkout. Once we receive the HTTP requests for each step of the workflow, we can create a method for each user action. The developers who wrote the application we are testing can provide us with the format of the corresponding requests during real development.

For demoblaze.com we will define four functions corresponding to our four steps: login, clickProduct, addToCart, viewCart and the actual mainPage to open the main page.

Let's create a new locustfile.py and restart locust.

from locust import HttpUser, SequentialTaskSet, task, between
            
class User(HttpUser):    
    @task
    class SequenceOfTasks(SequentialTaskSet):
        wait_time = between(1, 5)
        @task
        def mainPage(self):
            self.client.get("/")
            self.client.get("https://api.demoblaze.com/entries")
        @task
        def login(self):
            self.client.options("https://api.demoblaze.com/login")
            self.client.post("https://api.demoblaze.com/login",json={"username":"aaaa","password":"YWFhYQ=="})
            self.client.options("https://api.demoblaze.com/check")
            self.client.get("https://api.demoblaze.com/entries")
            self.client.post("https://api.demoblaze.com/check",json={"token":"YWFhYTE2MzA5NDU="})            
        @task
        def clickProduct(self):
            self.client.get("/prod.html?idp_=1")
            self.client.options("https://api.demoblaze.com/check")
            self.client.options("https://api.demoblaze.com/view")
            self.client.post("https://api.demoblaze.com/check",json={"token":"YWFhYTE2MzA5NDU="})
            self.client.post("https://api.demoblaze.com/view",json={"id":"1"})
        @task
        def addToCart(self):
            self.client.options("https://api.demoblaze.com/addtocart")
            self.client.post("https://api.demoblaze.com/addtocart",json={"id":"fb3d5d23-f88c-80d9-a8de-32f1b6034bfd","cookie":"YWFhYTE2MzA5NDU=","prod_id":1,"flag":'true'})
        @task 
        def viewCart(self):
            self.client.get("/cart.html")
            self.client.options("https://api.demoblaze.com/check")
            self.client.options("https://api.demoblaze.com/viewcart")
            self.client.post("https://api.demoblaze.com/check",json={"token":"YWFhYTE2MzA5NDU="})
            self.client.post("https://api.demoblaze.com/viewcart",json={"cookie":"YWFhYTE2MzA5NDU=","flag":'true'})
            self.client.options("https://api.demoblaze.com/view")
            self.client.post("https://api.demoblaze.com/check",json={"token":"YWFhYTE2MzA5NDU="})
            self.client.post("https://api.demoblaze.com/view",json={"id":"1"})

As you can see, all methods are included in the class using a sequential set of tasks, so they can be executed sequentially in the same order in which they were declared. Additionally, using wait_time we can add a pause between tasks. In this case, it is a random pause from 1 to 5 seconds.

You can also see that API requests are written using the full URL, since the Locust web user interface only allows one URL in the host field.

When launching, I specified 10 users in 10 seconds and at the beginning we even had errors, which is good news.

Various tokens

The next step in the development of our test is to get rid of the hard-coded token. It's important to map dynamic parameters, and we know that the token changes every time the user logs in. The token may expire and if not parameterized, the script will stop working.

The token sent to /check can be retrieved from the /login response. We can then store it in a variable and use it in all subsequent queries that need it. In this way, we can make our test more intelligent, and in fact more vital.

For the purposes of this article, we will only have one user, although Locust allows you to run tests on behalf of multiple users.

The token received in the response will, for example, look like this:

"Auth_token: YWFhYTE2MzA1ODg="

To extract it from the response, you can use the following regular expression:

"Auth_token: (.+?)"

You then need to re-import the module and use the match method to store the extracted value in a variable.

So, to extract the token from the response, we will first store the response in a variable like this:

response = self.client.post("https://api.demoblaze.com/login",json={"username":"aaaa","password":"YWFhYQ=="})

Now we can define a global variable and extract the token using a regular expression.

global token 
  token = re.match("\"Auth_token: (.+?)\"",response.text)[1]

And we can use this variable in the following queries

self.client.post("https://api.demoblaze.com/check",json={"token":token}

Now our login function will look like this:

 @task
        def login(self):
            self.client.options("https://api.demoblaze.com/login")
            response = self.client.post("https://api.demoblaze.com/login",json={"username":"aaaa","password":"YWFhYQ=="})
            global token 
            token = re.match("\"Auth_token: (.+?)\"",response.text)[1]
            self.client.options("https://api.demoblaze.com/check")
            self.client.get("https://api.demoblaze.com/entries")
            self.client.post("https://api.demoblaze.com/check",json={"token":token})

And in all other functions, you can specify the token variable instead of a hardcoded token. Here's an example for clickProduct:

        @task
        def clickProduct(self):
            self.client.get("/prod.html?idp_=1")
            self.client.options("https://api.demoblaze.com/check")
            self.client.options("https://api.demoblaze.com/view")
            self.client.post("https://api.demoblaze.com/check",json={"token":token})
            self.client.post("https://api.demoblaze.com/view",json={"id":"1"})

Processing responses

Finally, let's try to create a test that confirms that the item was added to the cart correctly. Locust doesn't have many built-in functions, but with Python you can easily add the custom functions you need.

So response.failure(“Error message”) can be used to mark a request as having failed. For the assertion to work properly, this function should be used inside an if and a catch_response argument should be added to check the response, as shown below.

 @task 
        def viewCart(self):
            self.client.get("/cart.html")
            self.client.options("https://api.demoblaze.com/check")
            self.client.options("https://api.demoblaze.com/viewcart")
            self.client.post("https://api.demoblaze.com/check",json={"token":token})
            with self.client.post("https://api.demoblaze.com/viewcart",catch_response=True,json={"cookie":token,"flag":'true'}) as response:
                if '"prod_id":1' not in response.text:
                    response.failure("Assert failure, response does not contain expected prod_id")
            self.client.options("https://api.demoblaze.com/view")
            self.client.post("https://api.demoblaze.com/check",json={"token":token})
            self.client.post("https://api.demoblaze.com/view",json={"id":"1"})

To make sure the test works correctly, let's change the product ID to something that is not in the answer.

…

                if '"prod_id":123' not in response.text:

…

As a result, we get a stable set of failed requests.

Conclusion

As you can see, Locust is quite an interesting tool that allows you to automate the process of load testing applications. With Python, we can significantly complicate tests to obtain more accurate results.

In conclusion, all testers are invited to an open lesson on October 17, dedicated to ways to organize a test model. In class we will define what a good test model is; We will discuss ways to organize a test model, as well as the pros and cons of using different approaches. You can sign up for the lesson using this link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *