Locust Framework Extension
Project description
Locust Backpack
The Locust Backpack is a Locustio load testing framework extension that aims to simplify writing load test scripts while also adding some functionality and awesome quirks.
The main idea behind the module is "Endpoints as objects". This means less hardcoding, fewer decision blocks, smaller chances for mistakes and bugs, less stress and more fun.
Getting Started
Starting to use the Backpack is simple. After placing the module in your working directory, import it from the Locust script:
import locust_backpack as Backpack
At this point, you are a proud user of the Locust Backpack. Time to put it to work.
Initialization
As mentioned earlier, the Backpack ideology is "Endpoints as objects". Thus, we have to create objects from all the endpoints which will later be used in the load test.
This can be done outside the TaskSet child class, in a different file, or just wherever we prefer.
The master class holding the endpoint data is LocustEndpoint. Each individual endpoint has to be initialized with an instance of this class, accepting the following parameters:
LocustEndpoint(self, endpoint, methods, name=None, headers=None, body=None)
Argument | Description | Default |
---|---|---|
endpoint (str) | string representing the actual endpoint |
N/A |
methods (str or list) | string or list containing all the methods the endpoint supports |
N/A |
name (str) | string representing a friendly or a debug name for the endpoint. This is the name the endpoint will have on the Locust UI |
name = endpoint |
headers(dict) | the headers this endpoint will use |
{} |
body(dict) | the body this endpoint will use |
{} |
Note: The values passed when initializing a LocustEndpoint are not permanent. These can be modified anytime during the test !
Examples:
Only the mandatory parameters:
TeamInit = Backpack.LocustEndpoint('/public/team/v1/init', 'post')
PvP2Init = Backpack.LocustEndpoint('/public/pvp2/v1/init', ['post', 'get'])
Passing a custom name
ResetSession = Backpack.LocustEndpoint('/public/session/v1/cheat/reset','post',
name="/public/session/v1/cheat/reset::newUser")
Passing a body
SessionStart = Backpack.LocustEndpoint('/public/session/v1/start', 'post',
name="/public/session/v1/start::newUser",
body={"device_type": "test",
"device_id": "test",
"version": 320,
"language": "RO",
"gender": 1,
"name": "load_test",
"platform": 1})
Once all our LocustEndpoints are created, we need access to the TaskSet in order to be able to make requests using Locust. This can be achieved in two ways:
Looping through a list of all endpoints and applying their attach() method to the current context:
for endp in [TeamInit, ResetSession, SessionStart, PvP2Init, EventInit]:
endp.attach(self)
Calling the Backpack method Unzip() which does the same as above but saves you from providing a list of LocustEndpoints
Backpack.Unzip(self)
Making Requests
Simple Request
Remember self.client.post(...)
? Forget about it.
Locust Backpack exposes a much simpler way of making a request and tracking it's result:
TeamInit.Request()
or
TeamInit.Request('post')
Calling Request() without a parameter will use the endpoint's passed method when initializing the LocustEndpoint. Specifying a method will use that method from the method pool.
Note: If LocustEndpoint was initialized with a list of methods and Request() is called without arguments, a MissingMethodException will be thrown !
But what happens under the hood when we LocustEndpoint.Request()
?
- A self.client.method(*args) request is made, following the Locust style and using the default or provided method
- A status code check is performed, registering the request.success or request.failure for Locust
- A Result object is created, containing all the request information
Simple-Chaining Dependent Requests
What if we want to connect multiple requests, but we want each subsequent request to be made only if the previous one succeeds? It couldn't be any easier!
ResetSession + SessionStart
...seriously, that's all...
The above is translated to:
- Make a request to ResetSession
- Check the result of ResetSession
- If ResetSession is successful, make a request to SessionStart
Of course, any number of requests can be chained:
TeamInit + ResetSession + SessionStart + EventInit
Let's say TeamInit
fails in the above scenario. In such case, no request following it will be performed because the dependency chain has failed from that point.
Note: Simple-Chaining has no wait time between requests. If successful, each request will be made instantly after the previous one
Note: Simple-Chaining only support LocustEndpoints that were declared with a single method
Advanced-Chaining Dependent Requests
To address the limitations of Simple-Chaining, Advanced-Chaining was created. Advanced-Chaining is defined by the backpack class Chain() as such:
Chain(self, endpoints, independent=False, wait=0)
Argument | Description | Default |
---|---|---|
endpoints (list of tuples ) | list of tuples containing an endpoint and it's method |
N/A |
independent(bool) | wether or not the provided endpoints depend on the success of the previous one |
False |
wait(int) | integer representing how much time to wait between making the provided requests |
0 |
Example:
Backpack.Chain([(PvP2Init, 'get'), (PvP2Init, 'post')], wait=2)
Scenarios
What if there was an easy way to build and run a scenario? Well there isn't one way to do it, but multiple.
Simple Scenario
The most basic way of building a scenario is the backpack's Scenario() class.
Scenario(self, endpoints, wait=1, runtime=None, independent_requests=False)
Argument | Description | Default |
---|---|---|
endpoints (list of tuples ) | list of tuples containing an endpoint and it's method |
N/A |
wait(int) | integer representing how much time to wait(sec) between making the provided requests |
1 |
runtime(int) | integer representing the runtime(sec) of the scenario |
None |
independent_requests(bool) | wether or not the provided endpoints depend on the success of the previous one |
False |
Example:
scenario_endpoints = [(TeamInit, 'post'), (ResetSession, 'post'),
(SessionStart, 'post'), (PvP2Init, 'get'),
(PvP2Init, 'post'), (EventInit, 'post')]
SCENARIO = backpack.Scenario(scenario_endpoints, runtime=10, independent_requests=True)
But this will not run the Scenario yet! Two methods are available for running the scenario:
SCENARIO.run()
Or if you would like to run the scenario only once, regardless of runtime:
SCENARIO.run_once()
Weighted Scenario
Maybe we want to add a chance for endpoints when running a scenario. For that purpose, WeightedScenario() comes into play. Subclassing the Simple Scenario, there are only two minor differences between them:
- WeightedScenario() does not have an independent_requests argument
- The tuples passed to WeightedScenario() have a 3rd element representing an int between 1-100 which is that chance for that request to be made (100 meaning 100% chance)
Example:
scenario_endpoints = [(TeamInit, 'post', 100), (ResetSession, 'post', 20),
(SessionStart, 'post', 100), (PvP2Init, 'get', 75),
(PvP2Init, 'post', 15), (EventInit, 'post', 50)]
SCENARIO_2 = Backpack.WeightedScenario(scenario_endpoints, runtime=15)
SCENARIO_2.run()
or
SCENARIO.run_once()
Sequenced Scenario (WIP)
One other way of building a scenario is using the Backpack.SequenceScenario() feature
What this scenario builder has special is it's ability to receive a list-of-lists-of-tuples or a list-of-tuples-of-lists-of-tuples.
Example:
First, we define some sequences. These are lists of tuples containing the LocustEndpoint, it's method and an optional weight/chance
sequence_1 = [(TeamInit, 'post', 10), (ResetSession, 'post', 20)]
sequence_2 = [(SessionStart, 'post', 30), (PvP2Init, 'get', 20)]
sequence_3 = [(PvP2Init, 'post', 50), (EventInit, 'post', 80)]
Then we build the sequenced scenario by passing the sequences above and an optional weight/chance
SCENARIO_3 = Backpack.SequenceScenario([sequence_1, sequence_2, sequence_3])
OR
SCENARIO_3 = Backpack.SequenceScenario([(sequence_1, 40), (sequence_2, 70), (sequence_3, 25)])
Then we can finally call the run() method:
SCENARIO_3.run()
or
SCENARIO_3.run_once()
The Result Object
Whenever a LocustEndpoint makes a request, a Result object is created. Every subsequent request to that LocustEndpoint overwrites the previous Result object.
This Result object holds information about the latest request to it's endpoint and has a few useful attributes as well:
LocustEndpoint.Result.success # Equals True if request is successful
LocustEndpoint.Result.failure # Equals True if request has failed
LocustEndpoint.Result.status_code # Equals the status code of the request
LocustEndpoint.Result.response # Contains the bare response of the request
LocustEndpoint.Result.json() # Tries to return the json-formatted response
Dependency building
The Locust Backpack is a deep one, with many pockets.
Let's assume we have an endpoint that needs an element from the response of another endpoint. Of course, we could parse the response and assign that specific key/element to the other endpoint, but why go through all that trouble every time?
For example, let's say we have an endpoint EventInit
that needs the key 'player'
from another endpoint SessionStart
We can permanently declare this dependency anywhere in the Locust script with the following:
EventInit >> DEPENDS_ON('player') >> SessionStart
Once this is done, every successful request to SessionStart
will assign it's player
key to EventInit
's body, making EventInit
use it in future requests.
Other features
Don't think that's all. This backpack is prepared for long journeys.
Sealing and Unsealing
Each LocustEndpoint has sealing and unsealing features which can be activated and deactivated with
EventInit.seal
EventInit.unseal
Sealing an endpoint means that the endpoint will be unable to make any further requests until being unsealed.
This does not interfere in any way with the script logic, but once a sealed endpoint request is reached, the request part is skipped and that endpoint's latest Result will be set to a silent fail (not registered anywhere)
There is also a way of sealing and unsealing all LocustEndpoints at once:
Backpack.SealAll()
Backpack.UnsealAll()
Check a Request's Success
Aside from using the Result() object to access all information regarding the latest request, including the success with EventInit.Result.success
, we can use a simpler and quicker way:
More specifically, we can check for the truthness of a LocustEndpoint instance
if EventInit: # Your code
In other words, the following two methods have the exact same result:
if EventInit.Result.success: # Code
if EventInit: # Code
Mass Assignation
As already mentioned, attributes of a LocustEndpoint can be changed anytime. But what if you want to modify or create a variable for all (or more than one) LocustEndpoints?
This is achieved using Backpack.SetGlobal()
Argument | Description | Default |
---|---|---|
variable (str) | the variable that will be modified or created |
N/A |
value (anything) | value of variable |
N/A |
selective (list) | integer representing the runtime(sec) of the scenario |
All LocustEndpoints |
Example:
Setting the same header for all LocustEndpoints
Backpack.SetGlobal('headers', auth_header)
Or just for specific LocustEndpoints
Backpack.SetGlobal('headers', auth_header, selective=[EventInit, TeamInit])
Zipping (WIP)
Every unzipped backpack should be zipped back. It would be a pitty to lose anything from it.
To that effect, the Backpack function Zip() was created.
Calling Backpack.Zip()
from anywhere within your current test will save all the test data to a folder within your workspace.
Zip() will:
- Check if the Locust address "http://localhost:8089/" is accessible
- Download the Locust .csv files
- Build a clone of the Locust stats page
- Use seaborn to plot the test details
- Nicely save everything to a datestamped folder in your current working directory
Note! Due to the fact that all zipping operations are done on a localhost URL, Zip() should only be called by the Locust Master
Flow Chart of a LocustEndpoint Request
graph TB
A(LocustEndpoint) -- Request --> B(TaskSet.client.method)
B -- status_check --> C(Request Result)
C -- request.success --> D(LocustEndpoint.Result)
D --> F(Resolve dependencies)
C -- request.failure --> E(LocustEndpoint.Result)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for locustio_backpack-0.2.7-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c95b4856e57937ca3be3bcfd50c839a48e67e4c677661f588393b72b7e21ebaa |
|
MD5 | 0003451a4e088eb11d0024b34fcdcb3b |
|
BLAKE2b-256 | dddcfd74494271a9052388f159deb87581e710b7abc162e1e1ab3528a1ecdc7c |