class MyUser(HttpUser): host = "https://postman-echo.com" wait_time = constant(180) # be nice to postman-echo def on_start(self): print("4. A user was started") # This is a good place to fetch user-specific test data. It is executed once per User # If you do not want the request logged, you can replace self.client.<method> with requests.<method> self.user_specific_testdata = self.client.post( "https://postman-echo.com/post", data="user-specific_" + timestring(), ).json()["data"] @task def t(self): self.client.get(f"/get?{global_test_data}") self.client.get(f"/get?{test_run_specific_data}") self.client.get(f"/get?{self.user_specific_testdata}") print("5. Getting task-run-specific testdata") # If every iteration is meant to use new test data this is the most common way to do it task_run_specific_testdata = self.client.post( "https://postman-echo.com/post", data="task_run_specific_testdata_" + timestring(), ).json()["data"] self.client.get(f"/get?{task_run_specific_testdata}") def on_stop(self): # this is a good place to clean up/release any user-specific test data print("a user was stopped")
class MyOtherRestUser(RestUserThatLooksAtErrors): host = "https://postman-echo.com" wait_time = constant(180) # be nice to postman-echo.com, and dont run this at scale. @task def t(self): with self.rest("GET", "/") as _resp: pass
class User(object, metaclass=UserMeta): """ Represents a "user" which is to be spawned and attack the system that is to be load tested. The behaviour of this user is defined by its tasks. Tasks can be declared either directly on the class by using the :py:func:`@task decorator <locust.task>` on methods, or by setting the :py:attr:`tasks attribute <locust.User.tasks>`. This class should usually be subclassed by a class that defines some kind of client. For example when load testing an HTTP system, you probably want to use the :py:class:`HttpUser <locust.HttpUser>` class. """ host: str = None """Base hostname to swarm. i.e: http://127.0.0.1:1234""" min_wait = None """Deprecated: Use wait_time instead. Minimum waiting time between the execution of locust tasks""" max_wait = None """Deprecated: Use wait_time instead. Maximum waiting time between the execution of locust tasks""" wait_time = constant(0) """ Method that returns the time (in seconds) between the execution of locust tasks. Can be overridden for individual TaskSets. Example:: from locust import User, between class MyUser(User): wait_time = between(3, 25) """ wait_function = None """ .. warning:: DEPRECATED: Use wait_time instead. Note that the new wait_time method should return seconds and not milliseconds. Method that returns the time between the execution of locust tasks in milliseconds """ tasks: List[Union[TaskSet, Callable]] = [] """ Collection of python callables and/or TaskSet classes that the Locust user(s) will run. If tasks is a list, the task to be performed will be picked randomly. If tasks is a *(callable,int)* list of two-tuples, or a {callable:int} dict, the task to be performed will be picked randomly, but each task will be weighted according to its corresponding int value. So in the following case, *ThreadPage* will be fifteen times more likely to be picked than *write_post*:: class ForumPage(TaskSet): tasks = {ThreadPage:15, write_post:1} """ weight = 10 """Probability of user class being chosen. The higher the weight, the greater the chance of it being chosen.""" abstract = True """If abstract is True, the class is meant to be subclassed, and locust will not spawn users of this class during a test.""" environment = None """A reference to the :py:attr:`environment <locust.Environment>` in which this locust is running""" client = None _state = None _greenlet: greenlet.Greenlet = None _group: Group _taskset_instance = None def __init__(self, environment): super().__init__() self.environment = environment def on_start(self): """ Called when a User starts running. """ pass def on_stop(self): """ Called when a User stops running (is killed) """ pass def run(self): self._state = LOCUST_STATE_RUNNING self._taskset_instance = DefaultTaskSet(self) try: # run the TaskSet on_start method, if it has one self.on_start() self._taskset_instance.run() except (GreenletExit, StopUser): # run the on_stop method, if it has one self.on_stop() def wait(self): """ Make the running user sleep for a duration defined by the User.wait_time function. The user can also be killed gracefully while it's sleeping, so calling this method within a task makes it possible for a user to be killed mid-task even if you've set a stop_timeout. If this behaviour is not desired, you should make the user wait using gevent.sleep() instead. """ self._taskset_instance.wait() def start(self, group: Group): """ Start a greenlet that runs this User instance. :param group: Group instance where the greenlet will be spawned. :type gevent_group: gevent.pool.Group :returns: The spawned greenlet. """ def run_user(user): """ Main function for User greenlet. It's important that this function takes the user instance as an argument, since we use greenlet_instance.args[0] to retrieve a reference to the User instance. """ user.run() self._greenlet = group.spawn(run_user, self) self._group = group return self._greenlet def stop(self, force=False): """ Stop the user greenlet. :param force: If False (the default) the stopping is done gracefully by setting the state to LOCUST_STATE_STOPPING which will make the User instance stop once any currently running task is complete and on_stop methods are called. If force is True the greenlet will be killed immediately. :returns: True if the greenlet was killed immediately, otherwise False """ if force or self._state == LOCUST_STATE_WAITING: self._group.killone(self._greenlet) return True elif self._state == LOCUST_STATE_RUNNING: self._state = LOCUST_STATE_STOPPING return False def context(self) -> Dict: """ Adds the returned value (a dict) to the context for :ref:`request event <request_context>` """ return {}
class MyUser(RestUser): host = "https://postman-echo.com" wait_time = constant(180) # be nice to postman-echo.com, and dont run this at scale. @task def t(self): # should work with self.rest("GET", "/get", json={"foo": 1}) as resp: if resp.js["args"]["foo"] != 1: resp.failure(f"Unexpected value of foo in response {resp.text}") # should work with self.rest("POST", "/post", json={"foo": 1}) as resp: if resp.js["data"]["foo"] != 1: resp.failure(f"Unexpected value of foo in response {resp.text}") # assertions are a nice short way of expressiont your expectations about the response. The AssertionError thrown will be caught # and fail the request, including the message and the payload in the failure content assert resp.js["data"]["foo"] == 1, "Unexpected value of foo in response" # to guard against complete failures (which may make resp.js None), only do the check when error is not already set: assert resp.error or resp.js["data"]["foo"] == 1 # RestResponse support safe navigation returning None if fields are missing (instead of throwing KeyError or with self.rest("POST", "/post", json={"foo": 1}) as resp: if resp.js["field that doesnt exist"]["status"] != "success": resp.failure(f"Bad or missing status in {resp.text}") # assertions are a nice short way to validate the response. The AssertionError they raise # will be caught by rest() and mark the request as failed with self.rest("POST", "/post", json={"foo": 1}) as resp: # mark the request as failed with the message "Assertion failed" assert resp.js["foo"] == 2 with self.rest("POST", "/post", json={"foo": 1}) as resp: # custom failure message assert resp.js["foo"] == 2, "my custom error message" with self.rest("POST", "/post", json={"foo": 1}) as resp: # use a trailing comma to append the response text to the custom message assert resp.js["foo"] == 2, "my custom error message with response text," # this only works in python 3.8 and up, so it is commented out: # if sys.version_info >= (3, 8): # with self.rest("", "/post", json={"foo": 1}) as resp: # # assign and assert in one line # assert (foo := resp.js["foo"]) # print(f"the number {foo} is awesome") # rest() catches most exceptions, so any programming mistakes you make automatically marks the request as a failure # and stores the callstack in the failure message with self.rest("POST", "/post", json={"foo": 1}) as resp: 1 / 0 # pylint: disable=pointless-statement # response isnt even json, but RestUser will already have been marked it as a failure, so we dont have to do it again with self.rest("GET", "/") as resp: pass with self.rest("GET", "/") as resp: # If resp.js is None (which it will be when there is a connection failure, a non-json responses etc), # reading from resp.js will raise a TypeError (instead of an AssertionError), so lets avoid that: if resp.js: assert resp.js["foo"] == 2 # or, as a mildly confusing oneliner: assert not resp.js or resp.js["foo"] == 2 # 404 with self.rest("GET", "http://example.com/") as resp: pass # connection closed with self.rest("POST", "http://example.com:42/", json={"foo": 1}) as resp: pass