Loadtesting testen mit Locust
Was ist Load Testing?
Unter Load-Testing (deutsch Lasttests) versteht man das Testen der API-Endpunkte bbzw. allgemein auch sonstiger Programme, mit den zu erwartenden Zugriffszahlen. User werden dabei vom Testsystem emuliert. Load-Testing ist verwandt aber nicht gleichzusetzen mit Stress-Testing, welches genutzt wird, um ein System ans Limit zu bringen und letzendlich zu prüfen, wann das System einknickt. Eine DDoS-Attacke könnte zum Beispiel mit einem Stresstest simuliert werden.
LocustIO,
ein in Python geschriebenes Open-Source-Tool, wird für Lasttests von Webanwendungen verwendet. Es ist einfach und leicht mit einer Benutzeroberfläche zu verwenden, um die Testergebnisse anzuzeigen.
LocustIO
hilft uns dabei, die Benutzer zu emulieren, die diese Aufgaben in unserer Webanwendung ausführen. Die Grundidee der Leistungsmessung besteht darin, eine Anzahl von Anfragen für verschiedene Aufgaben zu stellen und diese Anfragen zu analysieren.
Installation von Locust
Zuerst installieren wir via pip
das Locust-Framework
in unserer virtuellen
Umgebung:
(eventenv) pip install locust
wir können auch gleich testen, ob die Installation erfolgreich war und lassen uns die Version anzeigen.
(eventenv) locust -V
locust 2.8.6
Anlegen eines Test-Skripts
Locust-Tests werden in Dateien abgebildet, ähnlich, wie man das mit Unit-Tests
macht. Wir erstellen also unter event_manager/events/
die Datei
locustfile.py
. Wenn dieser Dateiname gewählt wird, muss man später beim
Aufruf von Locust den Dateinamen nicht spezifieren.
In der Datei event_manager/events/locustfile
beginnen wir mit ein paar
Imports.
from locust import HttpUser, task, between
Danach definieren wir die Locust-Testklasse. Den Namen der Klasse können wir
frei vergeben, wir müssen allerdings von HttpUser
erben, damit alles
funktioniert. Wir nennen die Klasse EventManagerUser
.
Oberhalb der Klasse definieren wir ein paar Konstanten, die wir im Code später benötigen werden. Das sind zum Beispiel die URLs zu den API-Endpunkten die wir testen wollen oder die Zugangsdaten für einen User, um einen Token zu erstellen.
TOKEN_URL = "http://127.0.0.1:8000/api/users/token"
CATEGORIES_URL = "http://127.0.0.1:8000/api/events/category/"
EVENTS_URL = "http://127.0.0.1:8000/api/events/events/"
USERNAME = "Bob"
PASSWORD = "abc"
class EventManagerUser(HttpUser):
wait_time = between(1, 2)
@task
def add_page_request(self):
self.client.post(
"/api/page-request/",
json=dict(url=random.choice(test_urls)),
headers=dict(Authorization=f"Token {self.token}"),
)
@task
def event_categories_request(self):
self.client.get(
CATEGORIES_URL,
headers=dict(Authorization=f"Token {self.token}"),
)
@task
def event_events_request(self):
self.client.get(
EVENTS_URL,
headers=dict(Authorization=f"Token {self.token}"),
)
def on_start(self):
response = self.client.post(
TOKEN_URL, json=dict(username=USERNAME, password=PASSWORD)
)
if not response.status_code == 200:
raise ValueError
data = response.json()
self.token = data.get("token")
In der Klasse EventManagerUser
findet sich die on_start
-Methode, die immer zu Beginn eines
Locust-Tests aufgerufen wird. Hier ist ein guter Ort, die Api mit den
User-Daten anzufragen und einen Auth-Token zu holen. Mit dem Locust client
fragen wir via HTTP-POST die entsprechende URL an und senden Username
und
Passwort
mit. Falls das alles geklappt hat und die URL erreichbar war,
erhalten wir den Token, den wir für einige Api-Endpunkte benötigen werden.
Neben der on_start
-Methode finden sich jetzt Test-Methoden in der Klasse
EventManagerUser
, die mit dem @task
-Dekorator dekoriert sind. Das sind
die eigentlichen Testfunktionen. Hier sprechen wir die API-Endpunkte an und
senden im Header den Token mit. Für unsere beiden Test-Methoden nutzen wir die
GET
Methode.
Starten des Tests
Im Grunde kann es jetzt losgehen. Wir switchen ins Verzeichnis
event_manager/events
und starten den Locus-Server
(eventenv) locust
Wenn Locust
dort im Verzeichnis die Datei locustfile.py
findet, sollte Locust
fehlerfrei starten.
Wichtig: Der Runserver muss natürlich die ganze Zeit laufen, sonst können die Endpunkte nicht angesprochen werden.
Das Locust Backend öffnen
Jetzt ist es an der Zeit, den Lasttest durchzuführen. Dazu wechseln wir im
Browser auf die URL http://0.0.0.0:8089/
und sehen das Locust
Eingabefenster.
Hier können wir jetzt die Anzahl der User, die User-Spawnrate und den Host
angeben, den wir testen wollen. Da unsere Testapplikation auf 127.0.0.1:8000
läuft, wählen wir diese als Host.
Auf der Übersichtsseite sehen wir dann für jeden unserer Tasks einen Eintrag. Der Locust-Server läuft solange, bis wir ihn explizit stoppen.
Wir können Statistiken für jeden Endpunkt, Gesamtanforderungen, Medienresonanz, Antwortperzentil und RPS (Requests per Second) anzeigen. Locust bietet auch Diagramme mit der Statistik der Gesamtanfrage pro Sekunde, der Antwortzeit und der Erhöhung der Anzahl der Benutzer.
Es fällt auf, dass die Kategorie-Liste schon bei 10 Usern im Durchschnitt ca. 49.000 Millisekunden
benötigt, dass sind 49 Sekunden. Dieser Endpunkt müsste also nochmal kontrekt
überarbeit werden, vermutlich würde hier ein prefetch-related
das Problem
lösen. Wir können das prefetch-reated auch für verschachtelte Foreign-Key
Beziehungen anlegen: Die Autor-Objekte laden wir vor, indem wir
events__author
prefetchen.
def get_queryset(self):
order_by = self.request.query_params.get("ordering", None)
queryset = models.Category.objects.prefetch_related(
"events",
"events__author"
).all()
if order_by == "date":
queryset = queryset.annotate(
num_events=Count("events")
).order_by("-" + order_by)
return queryset