Wie du Unit-Tests in Flask durchführen

Einführung

Testen ist ein wesentlicher Bestandteil des Softwareentwicklungsprozesses, es stellt sicher, dass der Code wie erwartet verhält und frei von Fehlern ist. In Python ist pytest ein populäres Testframework, das mehrere Vorteile gegenüber dem Standard-unit test-Modul bietet, das ein integriertes Python-Testframework ist und Teil der Standardbibliothek ist. pytest bietet eine einfachere Syntax, bessere Ausgaben, leistungsstarke Fixtures und eine reiche Plugin-Ökosystem. Dieser Leitfaden wird Ihnen bei der Konfiguration einer Flask-Anwendung, der Integration von pytest-Fixtures und der Schreibung von Unit-Tests mit pytest gehen.

Voraussetzungen

Vor dem Beginn benötigen Sie Folgendes:

  • Ein Server, der Ubuntu ausführt und einen nicht-root-Benutzer mit sudo-Berechtigungen und einer aktiven Firewall. Wenn Sie Hilfe benötigen, um dies zu setzen, wählen Sie Ihre Distribution aus dieser Liste und folgen unserem Leitfaden für die initiale Serverkonfiguration. Stellen Sie sicher, dass Sie mit einer unterstützten Version von Ubuntu arbeiten.

  • Vertrautheit mit der Linux-Befehlszeile. Du kannst dich an dieser Anleitung über die Linux-Befehlszeile orientieren.

  • Ein grundlegendes Verständnis von Python-Programmierung und dem pytest-Testframework in Python. Du kannst dich auf unseren Leitfaden über das Pytest Python-Test Framework verlassen, um mehr über pytest zu erfahren.

  • Python 3.7 oder höher installiert auf Ihrem Ubuntu-System. Um zu lernen, wie Sie ein Python-Skript auf Ubuntu ausführen, können Sie sich an unserem Leitfaden über Wie man ein Python-Skript auf Ubuntu ausführt orientieren.

Warum pytest eine bessere Alternative zu unittest ist

pytest bietet mehrere Vorteile gegenüber dem integrierten unittest-Framework:

  • Pytest ermöglicht Ihnen, Tests mit weniger Schablone-Code zu schreiben, indem Sie einfache assert-Aussagen verwenden, anstatt der mehrflächige Methoden, die unittest erfordert.

  • Es bietet eine detailliertere und lesbarere Ausgabe, was es einfacher macht, zu erkennen, wo und warum ein Test fehlgeschlagen ist.

  • Pytest-Fixtures ermöglichen flexiblere und wiederverwendbarere Testsetups als die von unittest verwendeten setUp– und tearDown-Methoden.

  • Es erleichtert das Ausführen der gleichen Testfunktion mit mehreren Input-Sets, was in unittest nicht so einfach ist.

  • Pytest verfügt über eine reiche Sammlung von Plugins, die seine Funktionalität erweitern, von Code-Coverage-Tools bis zur Parallelausführung von Tests.

  • Es erkennt automatisch Testdateien und -funktionen anhand seiner Namenskonventionen, was die Zeit und den Aufwand beim Management von Test-Suites spart.

Anhand 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 – Einrichten des Umgebungs

Ubuntu 24.04 enthält Python 3 standardmäßig.Öffnen Sie die Konsole und führen Sie das folgende Kommando aus, um die Installation von Python 3 zu überprüfen:

root@ubuntu:~# python3 --version
Python 3.12.3

Wenn Python 3 bereits auf Ihrem Rechner installiert ist, liefert das oben genannte Kommando die aktuelle Version der Python 3-Installation zurück.Wenn es nicht installiert ist, können Sie das folgende Kommando ausführen und Python 3 installieren:

root@ubuntu:~# sudo apt install python3

Als nächstes müssen Sie den Paketinstaller pip auf Ihrem System installieren:

root@ubuntu:~# sudo apt install python3-pip

Sobald pip installiert ist, installieren wir Flask.

Schritt 2 – Erstellen einer Flask-Anwendung

Legen wir zuerst eine einfache Flask-Anwendung an.Erstellen Sie ein neues Verzeichnis für Ihr Projekt und wechseln Sie in es:

root@ubuntu:~# mkdir flask_testing_app
root@ubuntu:~# cd flask_testing_app

Nun werden wir ein virtuelles Umfeld erzeugen und aktivieren, um Abhängigkeiten zu verwalten:

root@ubuntu:~# python3 -m venv venv
root@ubuntu:~# source venv/bin/activate

Installieren wir Flask mit pip:

root@ubuntu:~# pip install Flask

Nun werden wir eine einfache Flask-Anwendung erstellen. Erstellen Sie ein neues Datei mit dem Namen app.py und fügen Sie folgenden Code hinzu:

app.py
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 verfügt über drei Routen:

  • /: Gibt eine einfache “Hallo, Flask!”-Nachricht zurück.
  • /about: Gibt eine einfache “Dies ist die Über-Seite”-Nachricht zurück.
  • /multiply/<int:x>/<int:y>: Multipliziert zwei Integer und gibt das Ergebnis zurück.

Um die Anwendung zu starten, führen Sie folgenden Befehl aus:

root@ubuntu:~# flask run
output
* Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Aus dem obenstehenden Output können Sie erkennen, dass der Server unter http://127.0.0.1 läuft und auf Port 5000 lauscht.Öffnen Sie in einem anderen Ubuntu-Terminal das folgende curl-Kommando nach dem anderen:

  • 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

Lass uns verstehen, was diese GET-Anfragen tun:

  1. curl http://127.0.0.1:5000/:
    Dies sendet eine GET-Anfrage an die Wurzelroute (‘/’) unserer Flask-Anwendung. Der Server antwortet mit einem JSON-Objekt, das die Nachricht “Hallo, Flask!” enthält, was die grundlegende Funktionalität unserer Home-Route demonstriert.

  2. curl http://127.0.0.1:5000/about:
    Dies sendet eine GET-Anfrage an die Route /about. Der Server antwortet mit einem JSON-Objekt, das die Nachricht “Dies ist die Über-Seite.” enthält. Dies zeigt an, dass unsere Route korrekt funktioniert.

  3. curl http://127.0.0.1:5000/multiply/10/20:
    Dies sendet eine GET-Anfrage an die Route /multiply mit zwei Parametern: 10 und 20. Der Server multipliziert diese Zahlen und antwortet mit einem JSON-Objekt, das das Ergebnis (200) enthält. Dies demonstriert, dass unsere Multiplikationsroute korrekt URL-Parameter verarbeiten und Rechenoperationen durchführen kann.

Diese GET-Anfragen ermöglichen es uns, mit der API-Endpunkte unserer Flask-Anwendung zu interagieren, um Informationen abzurufen oder Aktionen auf dem Server auszulösen, ohne Daten zu ändern. Sie sind nützlich, um Daten abzurufen, die Funktionalität von Endpunkten zu testen und zu verifizieren, dass unsere Routen wie erwartet reagieren.

Lassen Sie uns nun die jeweiligen GET-Anfragen in Aktion sehen:

root@ubuntu:~# curl http://127.0.0.1:5000/
Output
{"message":"Hello, Flask!"}
root@ubuntu:~# curl http://127.0.0.1:5000/about
Output
{"message":"This is the About page"}
root@ubuntu:~# curl http://127.0.0.1:5000/multiply/10/20
Output
{"result":200}

Schritt 3 – Installieren von pytest und Schreiben Ihrer ersten Testdatei

Nun, dass Sie eine grundlegende Flask-Anwendung haben, installieren wir pytest und schreiben uns eine Einführungstestdatei.

Installieren Sie pytest mit pip:

root@ubuntu:~# pip install pytest

Erstellen Sie ein Testsverzeichnis, um Ihre Testdateien aufzubewahren:

root@ubuntu:~# mkdir tests

Nun werde ich eine neue Datei namens test_app.py erstellen und den folgenden Code hinzufügen:

test_app.py
# Importiere das Modul sys, um die Laufzeitumgebung von Python zu ändern
import sys
# Importiere das Modul os, um mit dem Betriebssystem zu interagieren
import os

# Füge die Elternordner zu sys.path hinzu
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Importiere die Flask-Anwendungskonstante aus der Hauptanwendungsdatei
from app import app 
# Importiere pytest für das Schreiben und Ausführen von Tests
import pytest

@pytest.fixture
def client():
    """A test client for the app."""
    with app.test_client() as client:
        yield client

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

Lass uns die Funktionen in dieser Testdatei aufbrechen:

  1. @pytest.fixture def client():
    Dies ist ein pytest-Fixture, das einen Testclient für unsere Flask-App erstellt. Es verwendet die Methode app.test_client(), um einen Client zu erzeugen, der Anfragen an unsere App senden kann, ohne dass der eigentliche Server laufen muss. Der Ausdruck yield ermöglicht es, den Client in Tests zu verwenden und anschließend ordnungsgemäß zu schließen, nachdem jeder Test abgeschlossen ist.

  2. def test_home(client):

    Diese Funktion testet die Home-Route (/) unserer App. Sie sendet eine GET-Anfrage an diese Route mit dem Test-Client, dann stellt sie sicher, dass der Antwortstatuscode 200 (OK) ist und dass die JSON-Antwort dem erwarteten Nachricht entspricht.

  3. def test_about(client):

    Ähnlich wie test_home, testet diese Funktion die About-Route (/about). Sie prüft auf einen Statuscode von 200 und überprüft den Inhalt der JSON-Antwort.

  4. def test_multiply(client):

    Diese Funktion testet die Multiply-Route mit gültigen Eingaben (/multiply/3/4). Sie überprüft, dass der Statuscode 200 ist und dass die JSON-Antwort den korrekten Ergebnis der Multiplikation enthält.

  5. def test_multiply_invalid_input(client):
    Diese Funktion testet die Multiplikationsroute mit ungültigen Eingaben (multiply/three/four). Sie überprüft, dass der Statuscode 404 (Nicht gefunden) ist, was die erwartete Verhalten ist, wenn die Route die Texteingaben nicht auf die erforderlichen ganzzahligen Parameter abbilden kann.

  6. def test_non_existent_route(client):
    Diese Funktion testet das Verhalten der App, wenn eine nicht existierende Route aufgerufen wird. Sie sendet einen GET-Anfrage an /non-existent, die in unserer Flask-App nicht definiert ist. Der Test stellt sicher, dass der Antwortstatuscode 404 (Nicht gefunden) ist, um sicherzustellen, dass unsere App korrekt mit Anfragen zu nicht definierten Routen umgeht.

Diese Tests decken die grundlegende Funktionalität unserer Flask-App ab, sicherstellend, dass jede Route korrekt auf gültige Eingaben reagiert und dass die Multiplikationsroute ungültige Eingaben angemessen verarbeitet. Durch die Verwendung von pytest können wir diese Tests leicht ausführen, um sicherzustellen, dass unsere App wie erwartet funktioniert.

Schritt 4 – Die Tests ausführen

Um die Tests zu starten, führen Sie folgenden Befehl aus:

root@ubuntu:~# pytest

Standardmäßig durchsucht der pytest-Entdeckungsprozess rekursiv die aktuelle Verzeichnisstruktur und seine Unterordner nach Dateien, die mit den Namen „test_“ beginnen oder mit „_test“ enden. Die Tests in diesen Dateien werden dann ausgeführt. Sie sollten eine Ausgabe sehen, die etwa wie folgt aussieht:

Output
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 abgeschlossen wurden.

Schritt 5: Verwendung von Fixtures in pytest

Fixtures sind Funktionen, die zur Bereitstellung von Daten oder Ressourcen für Tests verwendet werden. Sie können verwendet werden, um Testumgebungen zu setzen und abzureißen, Daten zu laden oder andere Setup-Aufgaben auszuführen. In pytest werden Fixtures mithilfe des Dekorators @pytest.fixture definiert.

Hier ist, wie Sie die bestehende fixture verbessern können. Aktualisieren Sie die client fixture, um Setup- und Teardown-Logik zu verwenden:

test_app.py
@pytest.fixture
def client():
    """Set up a test client for the app with setup and teardown logic."""
    print("\nSetting up the test client")
    with app.test_client() as client:
        yield client  # Hier passiert der Test
    print("Tearing down the test client")

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

Diese Setup fügt druckenden Ausdrucken hinzu, um die Setup- und Tear-down-Phasen in der Testausgabe zu demonstrieren. Dies kann gegebenenfalls durch tatsächliches Ressourcenmanagement ersetzt werden.

Lass uns versuchen, die Tests erneut zu starten:

root@ubuntu:~# pytest -vs

Das -v-Flag erhöht die Detailstufe und das -s-Flag lässt druckende Ausdrucken in der Konsoleausgabe anzeigen.

Du solltest die folgende Ausgabe sehen:

Output
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app cachedir: .pytest_cache collected 5 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 ============================================ 5 passed in 0.35s =============================================

Schritt 6: Hinzufügen eines Fehler-Testfalles

Lass uns einen fehlgeschlagenen Testfall zum bestehenden Testdatei hinzufügen. Ändere die Datei test_app.py und füge am Ende die untenstehende Funktion hinzu, um einen fehlgeschlagenen Testfall für ein inkorrektes Ergebnis zu erzeugen:

test_app.py
def test_multiply_edge_cases(client):
    """Test the multiply route with edge cases to demonstrate failing tests."""
    # Test mit Null
    response = client.get('/multiply/0/5')
    assert response.status_code == 200
    assert response.json == {"result": 0}

    # Test mit großen Zahlen (dies könnte fehlschlagen, wenn nicht korrekt behandelt)
    response = client.get('/multiply/1000000/1000000')
    assert response.status_code == 200
    assert response.json == {"result": 1000000000000}

    # Absichtlicher fehlgeschlagener Test: inkorrektes Ergebnis
    response = client.get('/multiply/2/3')
    assert response.status_code == 200
    assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"

Lass uns den test_multiply_edge_cases-Funktion aufbrechen und erklären, was jedes Teil tut:

  1. Test mit Null:Dieser Test überprüft, ob die Multiplikationsfunktion korrekt Multiplikation durch Null behandelt. Wir erwarten das Ergebnis 0, wenn jede Zahl durch Null multipliziert wird. Dies ist ein wichtiger Edge Case zu testen, weil einige Implementierungen Probleme mit der Null-Multiplikation可能有.
  2. Test mit großen Zahlen:
    Dieser Test überprüft, ob die Multiplikationsfunktion große Zahlen ohne Überlauf oder Präzisionsprobleme behandeln kann. Wir multiplizieren zwei Millionen-Werte, erwartend ein Ergebnis von einer Billion. Dieser Test ist entscheidend, weil er die oberen Grenzen der Fähigkeiten der Funktion überprüft. Beachten Sie, dass dies möglicherweise fehlschlagen kann, wenn die Serverimplementierung keine großen Zahlen richtig behandelt, was auf das Bedürfnis nach Bibliotheken für große Zahlen oder eine andere Datentyp-Implementierung hinweisen könnte.

  3. Berechnungsvorgangsvorhersagefehler:
    Dieser Test ist absichtlich so eingerichtet, dass er versagt. Er überprüft, ob 2 * 3 gleich 7 ist, was falsch ist. Dieser Test zielt darauf ab, zu demonstrieren, wie ein fehlgeschlagener Test im Testausgang aussieht. Dies hilft dabei, zu verstehen, wie fehlgeschlagene Tests erkannt und debuggt werden können, was ein unerläßliches Fachwissen in testgetriebener Entwicklung undDebugging-Prozessen ist.

Durch die Beinhaltung dieser边缘Fälle und eines absichtlichen Fehlers werden nicht nur die grundlegenden Funktionen Ihrer Multiplikationsroute getestet, sondern auch ihr Verhalten unter Extrembedingungen und ihre Fehlerberichterstattung. Dieser Ansatz zur Testung hilft dabei, die Robustheit und Zuverlässigkeit unserer Anwendung sicherzustellen.

Lass uns versuchen, die Tests erneut auszuführen:

root@ubuntu:~# pytest -vs

Du solltest die folgende Ausgabe sehen:

Output
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 ================================================================= FAILURES ================================================================== _________________________________________________________ test_multiply_edge_cases __________________________________________________________ client = <FlaskClient <Flask 'app'>> def test_multiply_edge_cases(client): """Test the multiply route with edge cases to demonstrate failing tests.""" # Test mit Null response = client.get('/multiply/0/5') assert response.status_code == 200 assert response.json == {"result": 0} # Test mit großen Zahlen (dies könnte fehlschlagen, wenn nicht korrekt behandelt) response = client.get('/multiply/1000000/1000000') assert response.status_code == 200 assert response.json == {"result": 1000000000000} # Absichtlicher Testfehlschlag: falscher Ergebnis response = client.get('/multiply/2/3') assert response.status_code == 200 > assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case" E AssertionError: This test should fail to demonstrate a failing case E assert {'result': 6} == {'result': 7} E E Differing items: E {'result': 6} != {'result': 7} E E Full diff: E { E - 'result': 7,... E E ...Full output truncated (4 lines hidden), use '-vv' to show tests/test_app.py:61: AssertionError ========================================================== short test summary info ========================================================== FAILED tests/test_app.py::test_multiply_edge_cases - AssertionError: This test should fail to demonstrate a failing case ======================================================== 1 failed, 5 passed in 0.32s ========================================================

Der obenstehende Fehlerschlussfolgerung zeigt, dass der Test test_multiply_edge_cases in der Datei tests/test_app.py fehlgeschlagen ist. Insbesondere verursachte die letzte Assertion in dieser Testfunktion den Fehler.

Dieser absichtliche Fehler ist nützlich, um zu demonstrieren, wie Testfehlschläge gemeldet werden und welche Informationen in der Fehlerschlussfolgerung bereitgestellt werden. Es zeigt genau an, an der Stelle des Fehlers aufgetreten ist, die erwarteten und tatsächlichen Werte und den Unterschied zwischen beiden.

In einer realeinstellung würden Sie den Code beheben, um den Test zu bestehen, oder den Test anpassen, falls das erwarte Ergebnis falsch war. Allerdings ist in diesem Fall der Fehler absichtlich für didaktische Zwecke.

Fazit

In diesem Lehrbuch haben wir beschrieben, wie man Unit-Tests für eine Flask-Anwendung mit pytest einrichtet, integrierte pytest-Fixtures verwendet und gezeigt, wie ein Testfehler aussieht. Indem Sie diese Schritte befolgen, können Sie sicherstellen, dass Ihre Flask-Anwendungen zuverlässig und pflegbar sind, Bugs minimieren und die Codequalität verbessern.

Sie können sich an die Flask– und Pytest-Offizielle Dokumentation wenden, um mehr zu erfahren.

Source:
https://www.digitalocean.com/community/tutorials/unit-test-in-flask