You can achieve code reuse and a more organized structure in your Locust tests by inheriting from existing User classes and composing behaviors.
Here’s a simple Locust file demonstrating inheritance and composition:
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
@task
def index(self):
self.client.get("/")
class ProductPageUser(WebsiteUser):
@task
def view_product(self):
self.client.get("/products/123")
class CartUser(ProductPageUser):
@task
def add_to_cart(self):
self.client.post("/cart/add", json={"product_id": 123, "quantity": 1})
@task(10) # This task will run 10 times more often than others
def view_cart(self):
self.client.get("/cart")
This example shows how ProductPageUser inherits from WebsiteUser, gaining the index task. CartUser then inherits from ProductPageUser, inheriting both index and view_product tasks, and adds its own add_to_cart and view_cart tasks.
The fundamental problem this solves is managing complexity as your load tests grow. Without inheritance and composition, you’d end up with a lot of duplicated code, making it hard to update common test logic or create variations of user behaviors. For instance, if you needed to add a common header to all requests, you could add it to WebsiteUser, and all its subclasses would automatically get it.
Internally, Locust processes these classes by creating a single, combined set of tasks for each User class at runtime. When CartUser is instantiated, Locust effectively merges the tasks defined in CartUser, ProductPageUser, and WebsiteUser into a single pool of executable tasks for that user type. The @task decorator assigns weights, and Locust’s scheduler picks tasks based on these weights and the wait_time defined.
The key levers you control are:
- Inheritance: Use
class MyUser(BaseUser):to inherit tasks and attributes likewait_timeandhost. - Task Decorator (
@task): Assign tasks to a user class. The optional integer argument is a weight, influencing how often a task is chosen relative to others within the same class. - Composition: A user class can have tasks defined directly, and also inherit tasks from its parent classes. This allows you to build complex behaviors by combining simpler ones.
HttpUservs.User:HttpUserprovides aself.client(an instance ofHttpSession) pre-configured for HTTP requests, including automatic handling of cookies and response status codes.Useris more generic.
Most people don’t realize that when you override a task from a parent class, you can still call the parent’s task using super(). For example, if ProductPageUser wanted to also hit the index page before viewing a product, it could do this:
class ProductPageUser(WebsiteUser):
@task
def view_product(self):
super().index() # Call the index task from the parent
self.client.get("/products/123")
This allows for extending parent behavior rather than just replacing it, offering a powerful way to build upon existing test logic.
The next concept you’ll likely want to explore is how to dynamically generate tasks or user classes based on external data.