Wie Sie Unit-Tests in Flask durchführen
Tests sind ein wesentlicher Bestandteil des Softwareentwicklungsprozesses, um sicherzustellen, dass der Code wie erwartet funktioniert und fehlerfrei ist. In Python ist pytest ein beliebtes Test-Framework, das mehrere Vorteile gegenüber dem standardmäßigen unittest-Modul bietet, das in der Standardbibliothek enthalten ist. pytest zeichnet sich durch eine einfachere Syntax, bessere Ausgabe, leistungsstarke Fixtures und ein umfangreiches Plugin-Ökosystem aus. Dieses Tutorial führt Sie durch die Einrichtung einer Flask-Anwendung, die Integration von pytest-Fixtures und das Schreiben von Unit-Tests mit pytest.
Voraussetzungen
Bevor Sie beginnen, benötigen Sie Folgendes:
- Einen Server mit Ubuntu und einen Nicht-Root-Benutzer mit sudo-Rechten sowie eine aktivierte Firewall. Stellen Sie sicher, dass Sie eine unterstützte Version von Ubuntu verwenden.
- Grundkenntnisse in der Nutzung der Linux-Befehlszeile.
- Grundlegendes Verständnis der Python-Programmierung und des pytest-Test-Frameworks.
- Python 3.7 oder höher auf Ihrem Ubuntu-System installiert.
Warum pytest eine bessere Alternative zu unittest ist
pytest bietet mehrere Vorteile gegenüber dem integrierten unittest-Framework:
- pytest ermöglicht das Schreiben von Tests mit weniger Boilerplate-Code, indem einfache
assert-Anweisungen anstelle der ausführlicheren Methoden vonunittestverwendet werden. - Es liefert detailliertere und lesbarere Ausgaben, wodurch Fehler leichter zu identifizieren sind.
- pytest-Fixtures ermöglichen flexiblere und wiederverwendbare Testumgebungen im Vergleich zu den
setUp– undtearDown-Methoden vonunittest. - Es vereinfacht das Ausführen derselben Testfunktion mit mehreren Eingabewerten, was in
unittestnicht so einfach ist. - pytest verfügt über eine umfangreiche Sammlung von Plugins, die die Funktionalität erweitern, von Code-Coverage-Tools bis hin zur parallelen Testausführung.
- Es erkennt automatisch Testdateien und -funktionen, die seinen Namenskonventionen entsprechen, was Zeit und Aufwand bei der Verwaltung von Test-Suites spart.
Angesichts dieser Vorteile ist pytest oft die bevorzugte Wahl für moderne Python-Tests. Lassen Sie uns nun eine Flask-Anwendung einrichten und Unit-Tests mit pytest schreiben.
Schritt 1 – Einrichtung der Umgebung
Ubuntu 24.04 wird standardmäßig mit Python 3 ausgeliefert. Öffnen Sie das Terminal und führen Sie den folgenden Befehl aus, um die Python 3-Installation zu überprüfen:
root@ubuntu:~# python3 --version
Python 3.12.3
Wenn Python 3 bereits auf Ihrem System installiert ist, gibt der obige Befehl die aktuelle Version der Installation zurück. Falls es nicht installiert ist, können Sie den folgenden Befehl ausführen, um Python 3 zu installieren:
root@ubuntu:~# sudo apt install python3
Als nächstes müssen Sie den Paketmanager pip auf Ihrem System installieren:
root@ubuntu:~# sudo apt install python3-pip
Sobald pip installiert ist, können Sie Flask installieren.
Schritt 2 – Erstellen einer Flask-Anwendung
Beginnen Sie mit der Erstellung einer einfachen Flask-Anwendung. Erstellen Sie ein neues Verzeichnis für Ihr Projekt und navigieren Sie in dieses:
root@ubuntu:~# mkdir flask_testing_app
root@ubuntu:~# cd flask_testing_app
Nun erstellen und aktivieren Sie eine virtuelle Umgebung, um Abhängigkeiten zu verwalten:
root@ubuntu:~# python3 -m venv venv
root@ubuntu:~# source venv/bin/activate
Installieren Sie Flask mit pip:
root@ubuntu:~# pip install Flask
Nun erstellen Sie eine einfache Flask-Anwendung. Erstellen Sie eine neue Datei mit dem Namen app.py und fügen Sie den folgenden Code hinzu:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return jsonify(message="Hello, Flask!")
@app.route('/about')
def about():
return jsonify(message="This is the About page")
@app.route('/multiply/<int:x>/<int:y>')
def multiply(x, y):
result = x * y
return jsonify(result=result)
if __name__ == '__main__':
app.run(debug=True)
Diese Anwendung hat drei Routen:
/: Gibt eine einfache Nachricht „Hello, Flask!“ zurück./about: Gibt eine einfache Nachricht „This is the About page“ zurück./multiply/<int:x>/<int:y>: Multipliziert zwei ganze Zahlen und gibt das Ergebnis zurück.
Um die Anwendung auszuführen, verwenden Sie den folgenden Befehl:
root@ubuntu:~# flask run
Ausgabe
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: Dies ist ein Entwicklungsserver. Verwenden Sie ihn nicht in einer Produktionsumgebung.
Nutzen Sie stattdessen einen WSGI-Server für den Produktivbetrieb.
* Debug-Modus: aktiv
* Läuft unter http://127.0.0.1:5000/ (Zum Beenden STRG+C drücken)
Testen mit cURL
Öffnen Sie eine weitere Ubuntu-Konsole und führen Sie die folgenden cURL-Befehle nacheinander aus:
GET: curl http://127.0.0.1:5000/
GET: curl http://127.0.0.1:5000/about
GET: curl http://127.0.0.1:5000/multiply/10/20
Ausgabe
{"message":"Hello, Flask!"}
{"message":"This is the About page"}
{"result":200}
Diese GET-Anfragen ermöglichen es Ihnen, mit den API-Endpunkten Ihrer Flask-Anwendung zu interagieren, um Informationen abzurufen oder Aktionen auf dem Server auszulösen, ohne dabei Daten zu verändern.
Schritt 3 – Installation von pytest und Schreiben Ihres ersten Tests
Nachdem Sie nun eine grundlegende Flask-Anwendung erstellt haben, installieren wir pytest und schreiben einige Unit-Tests.
Installieren Sie pytest mit pip:
root@ubuntu:~# pip install pytest
Erstellen Sie ein tests-Verzeichnis, um Ihre Testdateien zu speichern:
root@ubuntu:~# mkdir tests
Nun erstellen Sie eine neue Datei namens test_app.py und fügen den folgenden Code hinzu:
# Importieren des sys-Moduls zur Modifikation der Python-Laufzeitumgebung
import sys
# Importieren des os-Moduls für Interaktionen mit dem Betriebssystem
import os
# Hinzufügen des übergeordneten Verzeichnisses zu sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Importieren der Flask-App-Instanz aus der Hauptdatei der Anwendung
from app import app
# Importieren von pytest zum Schreiben und Ausführen von Tests
import pytest
@pytest.fixture
def client():
"""Ein Testclient für die Anwendung."""
with app.test_client() as client:
yield client
def test_home(client):
"""Testet die Home-Route."""
response = client.get('/')
assert response.status_code == 200
assert response.json == {"message": "Hello, Flask!"}
def test_about(client):
"""Testet die About-Route."""
response = client.get('/about')
assert response.status_code == 200
assert response.json == {"message": "This is the About page"}
def test_multiply(client):
"""Testet die Multiply-Route mit gültigen Eingaben."""
response = client.get('/multiply/3/4')
assert response.status_code == 200
assert response.json == {"result": 12}
def test_multiply_invalid_input(client):
"""Testet die Multiply-Route mit ungültigen Eingaben."""
response = client.get('/multiply/three/four')
assert response.status_code == 404
def test_non_existent_route(client):
"""Testet den Zugriff auf eine nicht vorhandene Route."""
response = client.get('/non-existent')
assert response.status_code == 404
Verständnis der Testdatei
@pytest.fixture def client(): Erstellt einen Testclient für die Flask-Anwendung. Dieser verwendet die Methodeapp.test_client(), um Anfragen an die Anwendung zu senden, ohne den Server auszuführen. Dieyield-Anweisung ermöglicht die Nutzung des Clients in Tests und sorgt für dessen ordnungsgemäße Schließung nach jedem Test.def test_home(client): Testet die Home-Route (/). Sendet eine GET-Anfrage an die Route und prüft, ob der Statuscode 200 (OK) ist und die JSON-Antwort der erwarteten Nachricht entspricht.def test_about(client): Ähnlich wietest_home, jedoch für die About-Route (/about).def test_multiply(client): Testet die Multiply-Route mit gültigen Eingaben (/multiply/3/4) und prüft, ob das erwartete Ergebnis zurückgegeben wird.def test_multiply_invalid_input(client): Testet die Multiply-Route mit ungültigen Eingaben (/multiply/three/four) und erwartet einen 404-Fehler.def test_non_existent_route(client): Testet, ob eine nicht existierende Route korrekt mit einem 404-Fehler behandelt wird.
Diese Tests überprüfen die grundlegende Funktionalität der Flask-Anwendung und stellen sicher, dass jede Route auf gültige und ungültige Eingaben korrekt reagiert. pytest ermöglicht es, diese Tests einfach auszuführen und sicherzustellen, dass die Anwendung wie erwartet funktioniert.
Schritt 4 – Ausführen der Tests
Um die Tests auszuführen, verwenden Sie den folgenden Befehl:
root@ubuntu:~# pytest
Standardmäßig durchsucht pytest das aktuelle Verzeichnis und seine Unterverzeichnisse nach Testdateien, die mit test_ beginnen oder auf _test enden, und führt alle darin enthaltenen Tests aus.
Ausgabe
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /home/user/flask_testing_app
collected 5 items
tests/test_app.py .... [100%]
=======================================================
5 passed in 0.19s
=======================================================
Dies zeigt an, dass alle Tests erfolgreich bestanden wurden.
Schritt 5 – Verwendung von Fixtures in pytest
Fixtures sind Funktionen, die Daten oder Ressourcen für Tests bereitstellen. Sie können genutzt werden, um Testumgebungen vorzubereiten, Daten zu laden oder andere Setup-Aufgaben zu erledigen. In pytest werden Fixtures mit dem Dekorator @pytest.fixture definiert.
Hier ist eine erweiterte Version der client-Fixture mit Setup- und Teardown-Logik:
test_app.py
@pytest.fixture
def client():
"""Richtet einen Testclient für die Anwendung mit Setup- und Teardown-Logik ein."""
print("\nTest-Client wird eingerichtet")
with app.test_client() as client:
yield client # Hier finden die Tests statt
print("Test-Client wird abgebaut")
Diese Einrichtung fügt Print-Anweisungen hinzu, um das Setup und Teardown in der Testausgabe zu demonstrieren. Falls erforderlich, können diese durch tatsächliche Ressourcenverwaltungslogik ersetzt werden.
Führen Sie die Tests erneut aus:
root@ubuntu:~# pytest -vs
Das Flag -v erhöht die Ausgabe-Detailtiefe, und -s ermöglicht die Anzeige von Print-Anweisungen in der Konsole.
Schritt 6 – Hinzufügen eines fehlschlagenden Testfalls
Fügen Sie einen fehlschlagenden Testfall zur bestehenden Testdatei hinzu. Bearbeiten Sie die Datei test_app.py und fügen Sie die folgende Funktion am Ende ein, um einen Testfall mit einem falschen Ergebnis zu simulieren:
test_app.py
def test_multiply_edge_cases(client):
"""Testet die Multiply-Route mit Grenzwerten, um fehlschlagende Tests zu demonstrieren."""
# Test mit Null
response = client.get('/multiply/0/5')
assert response.status_code == 200
assert response.json == {"result": 0}
# Test mit großen Zahlen (kann fehlschlagen, wenn nicht korrekt behandelt)
response = client.get('/multiply/1000000/1000000')
assert response.status_code == 200
assert response.json == {"result": 1000000000000}
# Absichtlich fehlschlagender Test: falsches Ergebnis
response = client.get('/multiply/2/3')
assert response.status_code == 200
assert response.json == {"result": 7}, "Dieser Test sollte fehlschlagen, um einen Fehlerfall zu demonstrieren"
Erklärung der Funktion test_multiply_edge_cases
Schauen wir uns die Funktion test_multiply_edge_cases genauer an und erklären, was jeder Teil davon macht:
Test mit Null
Dieser Test prüft, ob die Multiply-Funktion die Multiplikation mit Null korrekt behandelt. Wir erwarten, dass das Ergebnis 0 ist, wenn eine beliebige Zahl mit Null multipliziert wird. Dies ist ein wichtiger Grenzfall, da einige Implementierungen Probleme mit der Multiplikation durch Null haben könnten.
Test mit großen Zahlen
Dieser Test überprüft, ob die Multiply-Funktion große Zahlen ohne Überlauf- oder Präzisionsprobleme verarbeiten kann. Wir multiplizieren zwei Zahlen mit jeweils einer Million und erwarten als Ergebnis eine Billion. Dieser Test ist entscheidend, um die Grenzen der Funktion zu testen. Beachten Sie, dass dieser Test fehlschlagen könnte, falls die Serverimplementierung große Zahlen nicht korrekt verarbeitet. Dies könnte auf die Notwendigkeit einer speziellen Bibliothek für große Zahlen oder eines anderen Datentyps hinweisen.
Absichtlich fehlschlagender Test
Dieser Test wurde bewusst so erstellt, dass er fehlschlägt. Er überprüft, ob 2 * 3 gleich 7 ergibt, was offensichtlich falsch ist. Ziel dieses Tests ist es, zu demonstrieren, wie ein fehlgeschlagener Test in der Testausgabe aussieht. Dies hilft, das Identifizieren und Debuggen von fehlgeschlagenen Tests besser zu verstehen – eine essenzielle Fähigkeit im testgetriebenen Entwicklungsprozess und in der Fehlersuche.
Durch die Aufnahme dieser Grenzfälle und eines absichtlichen Fehlers wird nicht nur die grundlegende Funktionalität der Multiply-Route getestet, sondern auch deren Verhalten unter extremen Bedingungen und ihre Fehlerberichterstattung. Dieser Ansatz trägt dazu bei, die Robustheit und Zuverlässigkeit unserer Anwendung sicherzustellen.
Test erneut ausführen
root@ubuntu:~# pytest -vs
Ausgabe
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /home/user/flask_testing_app
cachedir: .pytest_cache
collected 6 items
tests/test_app.py::test_home
Setting up the test client
PASSED
Tearing down the test client
tests/test_app.py::test_about
Setting up the test client
PASSED
Tearing down the test client
tests/test_app.py::test_multiply
Setting up the test client
PASSED
Tearing down the test client
tests/test_app.py::test_multiply_invalid_input
Setting up the test client
PASSED
Tearing down the test client
tests/test_app.py::test_non_existent_route
Setting up the test client
PASSED
Tearing down the test client
tests/test_app.py::test_multiply_edge_cases
Setting up the test client
FAILED
Tearing down the test client
================================================================= FEHLSCHLÄGE ==================================================================
_________________________________________________________ test_multiply_edge_cases __________________________________________________________
client = >
def test_multiply_edge_cases(client):
"""Testet die Multiply-Route mit Grenzfällen, um fehlschlagende Tests zu demonstrieren."""
# Test mit Null
response = client.get('/multiply/0/5')
assert response.status_code == 200
assert response.json == {"result": 0}
# Test mit großen Zahlen (kann fehlschlagen, falls nicht korrekt verarbeitet)
response = client.get('/multiply/1000000/1000000')
assert response.status_code == 200
assert response.json == {"result": 1000000000000}
# Absichtlich fehlschlagender Test: falsches Ergebnis
response = client.get('/multiply/2/3')
assert response.status_code == 200
> assert response.json == {"result": 7}, "Dieser Test sollte fehlschlagen, um einen Fehlerfall zu demonstrieren"
E AssertionError: Dieser Test sollte fehlschlagen, um einen Fehlerfall zu demonstrieren
E assert {'result': 6} == {'result': 7}
E
E Unterschiedliche Werte:
E {'result': 6} != {'result': 7}
E
E Vollständiger Unterschied:
E {
E - 'result': 7,...
E
E ...Ausgabe gekürzt (4 Zeilen verborgen), verwenden Sie '-vv', um alles anzuzeigen
tests/test_app.py:61: AssertionError
========================================================== Kurzfassung der Testergebnisse ==========================================================
FAILED tests/test_app.py::test_multiply_edge_cases - AssertionError: Dieser Test sollte fehlschlagen, um einen Fehlerfall zu demonstrieren
======================================================== 1 fehlgeschlagen, 5 bestanden in 0.32s ========================================================
Die obige Fehlermeldung zeigt, dass der Test test_multiply_edge_cases in der Datei tests/test_app.py fehlgeschlagen ist. Insbesondere die letzte Assertion in dieser Testfunktion hat den Fehler verursacht.
Dieser absichtlich herbeigeführte Fehler ist nützlich, um zu demonstrieren, wie Testfehler gemeldet werden und welche Informationen in der Fehlermeldung bereitgestellt werden. Die Ausgabe zeigt die genaue Zeile, in der der Fehler aufgetreten ist, den erwarteten und den tatsächlichen Wert sowie den Unterschied zwischen beiden.
In einem realen Szenario würden Sie entweder den Code korrigieren, damit der Test erfolgreich ausgeführt wird, oder die Testfälle anpassen, falls das erwartete Ergebnis nicht korrekt definiert wurde. In diesem speziellen Fall wurde der Fehler jedoch absichtlich eingebaut, um zu Schulungszwecken zu demonstrieren, wie fehlschlagende Tests in der Ausgabe erscheinen.
Fazit
In diesem Tutorial haben wir gelernt, wie man Unit-Tests für eine Flask-Anwendung mit pytest einrichtet, pytest-Fixtures integriert und demonstriert, wie ein fehlgeschlagener Test aussieht. Durch die Umsetzung dieser Schritte können Sie sicherstellen, dass Ihre Flask-Anwendungen zuverlässig und wartbar sind, wodurch Fehler minimiert und die Codequalität verbessert wird.
Sie können die offizielle Dokumentation von Flask und Pytest konsultieren, um weitere Informationen zu erhalten.


