Search code examples
pythonloadperformance-testingload-testinglocust

Create reusable testing scenarios (smoke, load, etc.) that can be triggered by a custom command line parameter using Locust


I have two goals right now:

  1. Create a bunch of common testing scenarios (smoke, load, etc.) that I could re-use by means of a custom command line parameter.
  2. Move init_command_line_parser event somewhere in my project, so that I didn't have to use it in every locustfile.py

I believe it should be possible (at least I know how to do that in k6), however struggle to find a solution.

As a result, the following code should run all tests using the load scenario:

locust --host=https://myhost.com -f=locustfile.py --scenario=load

That's a snippet of my project and I expect that final solution might look something like this:

├── helpers
│   ├── events.py
│   ├── scenarios.py
├── tests
│   ├── locustfile.py

locustfile.py

class FirstTest(TaskSet):
    @task
    def first_task(self):
        ...
        do_something
        ...

class FirstTestUser(FastHttpUser):
    tasks = [FirstTest]

scenarios.py

class Smoke(LoadTestShape):

    stages = [
        {"duration": 60, "users": 10, "spawn_rate": 10}
    ]

    def tick(self):
        run_time = self.get_run_time()

        for stage in self.stages:
            if run_time < stage["duration"]:
                tick_data = (stage["users"], stage["spawn_rate"])
                return tick_data

        return None


class Load(LoadTestShape):

    stages = [
        {"duration": 60, "users": 10, "spawn_rate": 10},
        {"duration": 120, "users": 70, "spawn_rate": 10},
        {"duration": 600, "users": 250, "spawn_rate": 10},
    ]

    def tick(self):
        run_time = self.get_run_time()

        for stage in self.stages:
            if run_time < stage["duration"]:
                tick_data = (stage["users"], stage["spawn_rate"])
                return tick_data

        return None

events.py

@events.init_command_line_parser.add_listener
def _(parser):
    parser.add_argument("--scenario", type=str, env_var="LOCUST_SCENARIO", default="")


@events.test_start.add_listener
def _(environment, **kw):
    if environment.parsed_options.scenario == "smoke":
        print("smoke scenario")  # for testing purposes only
        scenarios.Smoke
    elif environment.parsed_options.scenario == "load":
        scenarios.Load

I assume that adding listeners to configs might be helpful. That's only a suggestion, considering that I tried to play with it but so far didn't come up with something that works.


Solution

  • What if you put the different load shapes in their own files. Then you can do (using Locust version 2.11 or later)

    locust -f locustfile.py,smoke.py
    

    or

    locust -f locustfile.py,load.py