← Tất cả bài viết

Locust

A comprehensive guide to performance testing with Locust - from basics to advanced techniques


Locust is a powerful and easy-to-use load testing tool written in Python. In this article, I'll share my experience using Locust to test website and API performance, from basic concepts to advanced techniques.

What is Locust?

Locust is an open-source framework for load testing written in Python. It allows you to write test scenarios using Python code, making it easier to create complex test cases compared to traditional tools like Apache JMeter.

Advantages of Locust:

  • Write tests in Python - easy to learn and maintain
  • Intuitive web UI for test monitoring
  • Distributed testing - can run tests on multiple machines
  • Real-time metrics and charts
  • Support for HTTP, WebSocket and other protocols

Installation and Setup

# Install Locust
pip install locust
 
# Or use requirements.txt
echo "locust>=2.15.0" >> requirements.txt
pip install -r requirements.txt

Basic Structure of a Locust Test

A basic Locust test file has the following structure:

from locust import HttpUser, task, between
 
class WebsiteUser(HttpUser):
    wait_time = between(1, 3)  # Wait time between requests
    
    def on_start(self):
        """Runs when user starts testing"""
        pass
    
    @task
    def index_page(self):
        """Test homepage"""
        self.client.get("/")
    
    @task(2)  # Weight 2 - runs twice as often as other tasks
    def view_item(self):
        """Test viewing product"""
        self.client.get("/item/1")

Real-world Example: Testing API with Authentication

Based on my real-world experience, here's an example of testing an API with authentication:

import random
from locust import HttpUser, task, between
 
USER_CREDENTIALS = [
    ("user1", "password1"),
    ("user2", "password2")
]
 
class APIUser(HttpUser):
    wait_time = between(15, 30)  # Simulate user think time
    
    def on_start(self):
        """Login when starting test"""
        self.access_token = None
        self.user_id = None
        self.login()
        self.get_user_info()
    
    def login(self):
        """Login and store access token"""
        username, password = random.choice(USER_CREDENTIALS)
        response = self.client.post("/api/v1/auth/login", json={
            "username": username,
            "password": password
        })
        
        if response.status_code == 200:
            data = response.json()
            self.access_token = data.get("data", {}).get("accessToken")
    
    def get_auth_headers(self):
        """Helper to create Authorization header"""
        return {"Authorization": f"Bearer {self.access_token}"} if self.access_token else {}
    
    @task(1)
    def get_user_info(self):
        """Get user information"""
        response = self.client.get("/api/v1/auth/me", 
                                 headers=self.get_auth_headers())
        if response.ok:
            data = response.json().get("data", {})
            self.user_id = data.get("id")
    
    @task(2)
    def get_dashboard_stats(self):
        """Get dashboard statistics"""
        if self.user_id:
            params = {"userId": self.user_id}
            response = self.client.get("/api/v1/dashboard/stats", 
                                     params=params, 
                                     headers=self.get_auth_headers())

Important Concepts

1. User Classes

User classes define the behavior of virtual users. Each user will run tasks according to the defined order and timing.

2. Tasks

Tasks are actions that users perform. Use the @task decorator to mark a method as a task.

3. Wait Time

wait_time defines the waiting time between tasks:

  • between(min, max): Random time between min and max seconds
  • constant(wait_time): Fixed waiting time
  • constant_throughput(rate): Maintain fixed throughput

4. Task Weighting

Use @task(weight) to define the frequency of each task. Tasks with higher weights will run more often.

Running Locust Tests

Command Line

# Run test with locustfile.py
locust -f locustfile.py
 
# Run with specific host
locust -f locustfile.py --host=http://localhost:8000
 
# Run in headless mode (no web UI)
locust -f locustfile.py --host=http://localhost:8000 --headless --users 10 --spawn-rate 2 --run-time 60s

Web UI

After running the command above, open your browser and visit http://localhost:8089 to view Locust's web UI.

Monitoring and Metrics

Locust provides important real-time metrics:

  • RPS (Requests Per Second): Number of requests per second
  • Response Time: Average response time, 95th percentile
  • Failure Rate: Rate of failed requests
  • Active Users: Number of active users

Distributed Testing

To run tests on multiple machines: Master node:

locust -f locustfile.py --master

Worker nodes:

locust -f locustfile.py --worker --master-host=MASTER_IP

Best Practices

  • Think Time: Always add wait_time to simulate real user behavior
  • Data Management: Use random data to avoid cache effects
  • Error Handling: Handle errors gracefully and log thoroughly
  • Resource Cleanup: Clean up resources in on_stop()
  • Monitoring: Monitor system resources (CPU, memory, network) when running tests

Advanced Example: Testing with Custom Data

import csv
import random
from locust import HttpUser, task, between
 
class AdvancedAPIUser(HttpUser):
    wait_time = between(1, 5)
    
    def on_start(self):
        """Load test data from CSV file"""
        self.test_data = self.load_test_data()
        self.current_data = random.choice(self.test_data)
    
    def load_test_data(self):
        """Load data from CSV file"""
        data = []
        with open('test_data.csv', 'r') as file:
            reader = csv.DictReader(file)
            for row in reader:
                data.append(row)
        return data
    
    @task
    def create_resource(self):
        """Create resource with random data"""
        payload = {
            "name": f"Test_{random.randint(1000, 9999)}",
            "description": "Generated by Locust test",
            "data": self.current_data
        }
        
        response = self.client.post("/api/resources", 
                                  json=payload,
                                  headers={"Content-Type": "application/json"})
        
        if response.status_code == 201:
            # Save ID for use in subsequent requests
            self.resource_id = response.json().get("id")
    
    @task
    def update_resource(self):
        """Update created resource"""
        if hasattr(self, 'resource_id'):
            payload = {"status": "updated"}
            self.client.put(f"/api/resources/{self.resource_id}", 
                           json=payload)

Conclusion

Locust is a powerful tool for load testing, especially suitable for Python developers. With simple syntax, intuitive web UI, and scalability, Locust makes performance testing easier and more effective. When to use Locust:

  • You want to write tests in Python
  • Need to test APIs or web applications
  • Want a web UI to monitor tests
  • Need distributed testing
  • Want to customize complex test logic

When not to use Locust:

  • Testing protocols not supported (like gRPC, custom protocols)
  • Need to record and replay user actions
  • Team lacks Python experience

With Locust, you can easily create complex test scenarios and monitor system performance in real-time. This is an essential tool in the toolkit of any DevOps engineer or developer concerned with performance testing.