Cuando desarrollamos una API REST normalmente hacemos tests para comprobar que funciona correctamente.
Por ejemplo:
¿El GET devuelve los productos?
¿El POST crea correctamente un producto?
¿El DELETE elimina el recurso?
Pero hay otra pregunta importante: ¿Qué pasa si 100 usuarios utilizan nuestra API al mismo tiempo?
Ahí entran las pruebas de carga.
En este post vamos a conocer Locust y hacer nuestro primer test de carga sobre una API REST.
No vamos a construir una API complicada. Vamos a suponer que ya tenemos una API CRUD de productos y nos vamos a concentrar exclusivamente en probarla.
¿Qué es Locust? Locust es una herramienta para realizar pruebas de carga.
Una de sus características más interesantes es que los escenarios de prueba se escriben en Python.
En lugar de tener que configurar un montón de archivos, podemos describir el comportamiento de nuestros usuarios directamente mediante código.
La idea es sencilla: Locust crea usuarios virtuales que realizan requests contra nuestra aplicación y nos permite observar cómo responde el sistema bajo carga.
Para el ejemplo vamos a utilizar una API CRUD para administrar productos.
Un producto tiene:
Product
├── id
├── name
├── price
└── stock
Y la API expone las operaciones habituales:
GET /products
GET /products/{id}
POST /products
PUT /products/{id}
DELETE /products/{id}
Por ejemplo:
GET /products
podría devolver:
[
{
"id": 1,
"name": "Notebook",
"price": 1200,
"stock": 10
},
{
"id": 2,
"name": "Mouse",
"price": 30,
"stock": 50
}
]
Para nuestro primer test solamente vamos a utilizar:
GET /products
Locust es una aplicación Python.
Podemos instalarlo con:
pip install locust
Comprobamos que quedó instalado:
locust --version
Ahora creemos un archivo:
locustfile.py
Este archivo contendrá nuestro escenario de prueba.
Comencemos con algo muy simple:
from locust import HttpUser, task, between
class ProductUser(HttpUser):
wait_time = between(1, 3)
@task
def get_products(self):
self.client.get("/products")
Aunque son pocas líneas, acá ya estamos describiendo un usuario virtual.
Veamos qué significa cada parte.
class ProductUser(HttpUser):
HttpUser representa un usuario que realizará requests HTTP.
Cuando Locust ejecute nuestra prueba, creará usuarios virtuales basándose en esta clase.
Tenemos:
wait_time = between(1, 3)
Esto indica que el usuario esperará entre 1 y 3 segundos entre una tarea y otra.
Es importante porque normalmente un usuario real no realiza requests continuamente sin esperar.
Ahora tenemos:
@task
def get_products(self):
self.client.get("/products")
La anotación @task indica que este método representa una acción que nuestro usuario puede realizar.
En este caso, la acción es: consultar productos
Y finalmente:
self.client.get("/products")
realiza el request HTTP.
Es decir, nuestro escenario completo básicamente dice: "Soy un usuario que consulta la lista de productos y espero entre 1 y 3 segundos antes de volver a hacerlo."
Supongamos que nuestra API está corriendo en: http://localhost:8080
Podemos iniciar Locust con:
locust -f locustfile.py --host http://localhost:8080
Locust levantará su interfaz web.
Por defecto podremos acceder a: http://localhost:8089
Desde ahí podemos configurar nuestra prueba.
Por ejemplo:
Number of users: 10
Spawn rate: 2
Estamos diciendo: Queremos simular 10 usuarios y crear 2 usuarios nuevos por segundo hasta llegar a los 10.
Cada uno de esos usuarios ejecutará el comportamiento que definimos en ProductUser.
Locust nos permite hacer este tipo de preguntas:
- ¿Cuánto tarda?
- ¿Cuántos requests por segundo soporta?
- ¿Cuántos requests fallan?
- ¿Qué ocurre cuando aumentamos la cantidad de usuarios?
- ¿Cuánto tarda el 95% de los requests?
Por ejemplo, podríamos obtener resultados similares a:
GET /products
Requests: 10.000
Failures: 0
Average: 42 ms
Median: 35 ms
95%: 80 ms
99%: 120 ms
Requests/sec: 85
El dato que más llama la atención puede ser el percentil 95.
Si tenemos:
95% → 80 ms
significa que el 95% de los requests respondió en 80 ms o menos.
El 5% restante tardó más.
Esto suele ser mucho más útil que mirar únicamente el promedio.
Pero una aplicación real no tiene usuarios que solamente ejecutan un GET.
Un usuario podría:
GET /products
↓
GET /products/10
↓
POST /products
↓
GET /products
Y probablemente algunas operaciones ocurran con mayor frecuencia que otras.
Por ejemplo:
80% → GET /products
15% → GET /products/{id}
5% → POST /products
Locust nos permite modelar ese comportamiento mediante diferentes @task.
Pero eso lo dejamos para el próximo paso.
La idea fundamental que quiero llevarme de este primer ejemplo es muy simple: Locust no prueba solamente endpoints.
Podemos utilizar Python para describir el comportamiento de usuarios virtuales y luego ejecutar ese comportamiento contra nuestra aplicación bajo diferentes niveles de carga.
Dejo link: https://locust.io/
