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 von unittest verwendet 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– und tearDown-Methoden von unittest.
  • Es vereinfacht das Ausführen derselben Testfunktion mit mehreren Eingabewerten, was in unittest nicht 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:

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"}


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 Methode app.test_client(), um Anfragen an die Anwendung zu senden, ohne den Server auszuführen. Die yield-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 wie test_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:

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:

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

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.

Quelle: digitalocean.com

Jetzt 200€ Guthaben sichern

Registrieren Sie sich jetzt in unserer ccloud³ und erhalten Sie 200€ Startguthaben für Ihr Projekt.

Das könnte Sie auch interessieren: