In der Softwareentwicklung sind Tests zur Behandlung von Ausnahmen und Fehlern von entscheidender Bedeutung. Durch geeignete Tests kann die Zuverlässigkeit des Codes erhöht und das Auftreten unerwarteter Fehler vermieden werden. In diesem Artikel wird erläutert, wie Sie mit dem Python-Testframework pytest Ausnahmen und Fehlerbehandlungen effizient testen können. Wir erklären schrittweise alles, von der grundlegenden Einrichtung bis hin zur Prüfung benutzerdefinierter Ausnahmen und mehrerer Fehlerbehandlungen.
Grundlegende Konfiguration von pytest
pytest ist ein leistungsstarkes Testframework für Python, das sich einfach installieren und verwenden lässt. Befolgen Sie die folgenden Schritte, um pytest einzurichten.
Installation von pytest
Zuerst müssen Sie pytest installieren. Verwenden Sie dazu den folgenden Befehl, um die Installation über pip durchzuführen.
pip install pytest
Grundlegende Verzeichnisstruktur
Für die Verzeichnisstruktur des Projekts empfiehlt es sich, die folgende Struktur zu verwenden.
my_project/
├── src/
│ └── my_module.py
└── tests/
├── __init__.py
└── test_my_module.py
Erstellen der ersten Testdatei
Als Nächstes erstellen Sie eine Python-Datei für die Tests. Erstellen Sie beispielsweise eine Datei namens test_my_module.py
im Verzeichnis tests
und fügen Sie den folgenden Inhalt hinzu.
def test_example():
assert 1 + 1 == 2
Ausführen von Tests
Um Tests auszuführen, führen Sie den folgenden Befehl im Hauptverzeichnis des Projekts aus.
pytest
Dadurch erkennt pytest automatisch die Testdateien im Verzeichnis tests
und führt die Tests aus. Wenn die Tests erfolgreich sind, ist die Grundeinrichtung abgeschlossen.
Methoden zur Prüfung von Ausnahmen
Um sicherzustellen, dass Ausnahmen korrekt ausgelöst werden, bietet pytest eine sehr nützliche Methode. Hier wird erklärt, wie man prüft, ob eine bestimmte Ausnahme ausgelöst wird.
Ausnahmeprüfung mit pytest.raises
Um sicherzustellen, dass eine Ausnahme ausgelöst wird, verwenden Sie den Kontextmanager pytest.raises
. Im folgenden Beispiel wird getestet, ob eine Division durch Null auftritt.
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
In diesem Test wird überprüft, ob bei der Ausführung von 1 / 0
eine ZeroDivisionError
ausgelöst wird.
Überprüfung der Ausnahmemeldung
Manchmal ist es nicht nur wichtig, dass eine Ausnahme auftritt, sondern auch, dass eine bestimmte Fehlermeldung enthalten ist. In diesem Fall verwenden Sie den Parameter match
.
def test_zero_division_message():
with pytest.raises(ZeroDivisionError, match="division by zero"):
1 / 0
In diesem Test wird überprüft, ob ein ZeroDivisionError
auftritt und die Fehlermeldung „division by zero“ enthält.
Testen mehrerer Ausnahmen
Es ist auch möglich, mehrere Ausnahmen in einem einzigen Testfall zu testen, z. B. um zu überprüfen, ob unter verschiedenen Bedingungen unterschiedliche Ausnahmen auftreten.
def test_multiple_exceptions():
with pytest.raises(ZeroDivisionError):
1 / 0
with pytest.raises(TypeError):
'1' + 1
In diesem Test wird zuerst überprüft, ob ein ZeroDivisionError
auftritt, und dann, ob ein TypeError
auftritt.
Durch das ordnungsgemäße Testen von Ausnahmen kann sichergestellt werden, dass der Code ordnungsgemäß mit Fehlern umgeht, ohne unerwartetes Verhalten zu zeigen.
Überprüfung von Fehlermeldungen
Es ist wichtig, in Tests zu überprüfen, ob eine bestimmte Fehlermeldung enthalten ist. In diesem Abschnitt wird erläutert, wie Sie mit pytest Fehlermeldungen überprüfen können.
Überprüfung einer bestimmten Fehlermeldung
Es ist möglich zu testen, ob eine Ausnahme nicht nur auftritt, sondern auch eine bestimmte Fehlermeldung enthält. Verwenden Sie pytest.raises
und geben Sie die Fehlermeldung über den Parameter match
an.
import pytest
def test_value_error_message():
def raise_value_error():
raise ValueError("This is a ValueError with a specific message.")
with pytest.raises(ValueError, match="specific message"):
raise_value_error()
In diesem Test wird überprüft, ob ein ValueError
auftritt und die Nachricht „specific message“ enthält.
Überprüfung der Fehlermeldung mit regulären Ausdrücken
Wenn Fehlermeldungen dynamisch generiert werden oder eine Teilübereinstimmung überprüft werden soll, sind reguläre Ausdrücke hilfreich.
def test_regex_error_message():
def raise_type_error():
raise TypeError("TypeError: invalid type for operation")
with pytest.raises(TypeError, match=r"invalid type"):
raise_type_error()
In diesem Test wird überprüft, ob die Nachricht des TypeError
den Ausdruck „invalid type“ enthält. Indem Sie einen regulären Ausdruck an den Parameter match
übergeben, können Sie eine Teilübereinstimmung überprüfen.
Überprüfung benutzerdefinierter Fehlermeldungen
Benutzerdefinierte Ausnahmen können auf dieselbe Weise wie Standardausnahmen überprüft werden.
class CustomError(Exception):
pass
def test_custom_error_message():
def raise_custom_error():
raise CustomError("This is a custom error message.")
with pytest.raises(CustomError, match="custom error message"):
raise_custom_error()
In diesem Test wird überprüft, ob ein CustomError
auftritt und die Nachricht „custom error message“ enthält.
Die Überprüfung von Fehlermeldungen ist wichtig, um die Konsistenz der Fehlermeldungen für Benutzer und die Genauigkeit von Debug-Informationen sicherzustellen. Nutzen Sie pytest, um diese Tests effizient durchzuführen.
Testen mehrerer Ausnahmebehandlungen
Wenn eine Funktion mehrere Ausnahmen auslösen kann, ist es wichtig zu testen, ob jede Ausnahme ordnungsgemäß behandelt wird. In diesem Abschnitt wird erklärt, wie Sie mehrere Ausnahmebehandlungen mit pytest testen können.
Überprüfung mehrerer Ausnahmen in einem Test
Wenn eine Funktion unter verschiedenen Eingabewerten unterschiedliche Ausnahmen auslöst, sollten Sie testen, ob jede Ausnahme ordnungsgemäß behandelt wird.
import pytest
def error_prone_function(value):
if value == 0:
raise ValueError("Value cannot be zero")
elif value < 0:
raise TypeError("Value cannot be negative")
return True
def test_multiple_exceptions():
with pytest.raises(ValueError, match="Value cannot be zero"):
error_prone_function(0)
with pytest.raises(TypeError, match="Value cannot be negative"):
error_prone_function(-1)
In diesem Test wird überprüft, ob error_prone_function
bei einem Wert von 0 eine ValueError
und bei einem negativen Wert eine TypeError
auslöst.
Verwendung von parametrisierten Tests zur Überprüfung von Ausnahmen
Verwenden Sie parametrisierte Tests, um effizient zu testen, dass eine Funktion bei unterschiedlichen Eingabewerten unterschiedliche Ausnahmen auslöst.
@pytest.mark.parametrize("value, expected_exception, match_text", [
(0, ValueError, "Value cannot be zero"),
(-1, TypeError, "Value cannot be negative")
])
def test_error_prone_function(value, expected_exception, match_text):
with pytest.raises(expected_exception, match=match_text):
error_prone_function(value)
In diesem parametrisierten Test wird die Funktion mit unterschiedlichen Werten getestet, wobei die erwarteten Ausnahmen und Fehlermeldungen kombiniert werden.
Testen mehrerer Ausnahmen mit benutzerdefinierten Methoden
Eine Funktion, die mehrere Ausnahmen auslöst, kann auch effizient mit benutzerdefinierten Methoden getestet werden.
def test_custom_multiple_exceptions():
def assert_raises_with_message(func, exception, match_text):
with pytest.raises(exception, match=match_text):
func()
assert_raises_with_message(lambda: error_prone_function(0), ValueError, "Value cannot be zero")
assert_raises_with_message(lambda: error_prone_function(-1), TypeError, "Value cannot be negative")
In diesem Test wird die benutzerdefinierte Methode assert_raises_with_message
verwendet, um zu überprüfen, ob die Funktion bei bestimmten Eingaben die richtigen Ausnahmen und Fehlermeldungen auslöst.
Durch die Überprüfung mehrerer Ausnahmen in einem Testfall kann der Testcode vereinfacht und die Wartbarkeit erhöht werden. Nutzen Sie die Funktionen von pytest, um effiziente Tests für Ausnahmebehandlungen durchzuführen.
Testen von benutzerdefinierten Ausnahmen
Durch die Definition und Verwendung benutzerdefinierter Ausnahmen können die Fehlerbehandlung einer Anwendung klarer gestaltet und spezifische Fehlerzustände besser behandelt werden. In diesem Abschnitt wird erläutert, wie benutzerdefinierte Ausnahmen getestet werden können.
Definition einer benutzerdefinierten Ausnahme
Zuerst definieren Sie eine benutzerdefinierte Ausnahme. Erstellen Sie eine neue Ausnahmeklasse, indem Sie die eingebaute Ausnahmeklasse von Python erweitern.
class CustomError(Exception):
"""Die Basisklasse für benutzerdefinierte Ausnahmen"""
pass
class SpecificError(CustomError):
"""Eine benutzerdefinierte Ausnahme für spezifische Fehler"""
pass
Eine Funktion, die benutzerdefinierte Ausnahmen auslöst
Erstellen Sie als Nächstes eine Funktion, die unter bestimmten Bedingungen eine benutzerdefinierte Ausnahme auslöst.
def function_that_raises(value):
if value == 'error':
raise SpecificError("An error occurred with value: error")
return True
Testen von benutzerdefinierten Ausnahmen
Verwenden Sie pytest, um zu überprüfen, ob benutzerdefinierte Ausnahmen korrekt ausgelöst werden.
import pytest
def test_specific_error():
with pytest.raises(SpecificError, match="An error occurred with value: error"):
function_that_raises('error')
In diesem Test wird überprüft, ob die Funktion function_that_raises
eine SpecificError
auslöst und die Fehlermeldung wie erwartet ist.
Testen mehrerer benutzerdefinierter Ausnahmen
Wenn mehrere benutzerdefinierte Ausnahmen verwendet werden, sollten Sie testen, ob jede Ausnahme ordnungsgemäß behandelt wird.
class AnotherCustomError(Exception):
"""Eine weitere benutzerdefinierte Ausnahme"""
pass
def function_with_multiple_custom_errors(value):
if value == 'first':
raise SpecificError("First error occurred")
elif value == 'second':
raise AnotherCustomError("Second error occurred")
return True
def test_multiple_custom_errors():
with pytest.raises(SpecificError, match="First error occurred"):
function_with_multiple_custom_errors('first')
with pytest.raises(AnotherCustomError, match="Second error occurred"):
function_with_multiple_custom_errors('second')
In diesem Test wird überprüft, ob die Funktion function_with_multiple_custom_errors
bei verschiedenen Eingabewerten die richtigen benutzerdefinierten Ausnahmen auslöst.
Überprüfung der Nachricht benutzerdefinierter Ausnahmen
Es ist auch wichtig zu überprüfen, ob die Fehlermeldung einer benutzerdefinierten Ausnahme korrekt ist.
def test_custom_error_message():
with pytest.raises(SpecificError, match="An error occurred with value: error"):
function_that_raises('error')
In diesem Test wird überprüft, ob ein SpecificError
auftritt und die Fehlermeldung „An error occurred with value: error“ enthält.
Durch das Testen benutzerdefinierter Ausnahmen kann sichergestellt werden, dass die Anwendung Fehlerzustände korrekt handhabt und die Qualität und Zuverlässigkeit des Codes verbessert wird.
Anwendungsbeispiel: Fehlerprüfung bei APIs
Bei der Entwicklung von APIs ist die Fehlerbehandlung wichtig. Damit Clients geeignete Fehlermeldungen erhalten, müssen Fehlerprüfungen durchgeführt werden. In diesem Abschnitt wird erklärt, wie Sie die Fehlerbehandlung einer API mit pytest testen können.
Beispiel-API mit FastAPI
Zunächst definieren wir einen einfachen FastAPI-Endpunkt, der spezifische Fehler auslöst.
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=400, detail="Item ID cannot be zero")
if item_id < 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "name": "Item Name"}
Dieser Endpunkt gibt einen Fehler 400 zurück, wenn item_id
gleich 0 ist, und einen Fehler 404, wenn item_id
negativ ist.
Fehlerprüfung mit pytest und httpx
Verwenden Sie als Nächstes pytest und httpx, um die Fehlerprüfung der API durchzuführen.
import pytest
from httpx import AsyncClient
from main import app
@pytest.mark.asyncio
async def test_read_item_zero():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/items/0")
assert response.status_code == 400
assert response.json() == {"detail": "Item ID cannot be zero"}
@pytest.mark.asyncio
async def test_read_item_negative():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/items/-1")
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
In diesen Tests wird überprüft, ob die Anfragen an /items/0
und /items/-1
die erwarteten Fehlercodes und Fehlermeldungen zurückgeben.
Automatisierung und Effizienzsteigerung bei Fehlerprüfungen
Durch die Verwendung von parametrisierten Tests können verschiedene Fehlerfälle in einem einzigen Test zusammengefasst werden.
@pytest.mark.asyncio
@pytest.mark.parametrize("item_id, expected_status, expected_detail", [
(0, 400, "Item ID cannot be zero"),
(-1, 404, "Item not found"),
])
async def test_read_item_errors(item_id, expected_status, expected_detail):
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get(f"/items/{item_id}")
assert response.status_code == expected_status
assert response.json() == {"detail": expected_detail}
In diesem parametrisierten Test werden verschiedene item_id
-Werte getestet, um sicherzustellen, dass die erwarteten Statuscodes und Fehlermeldungen zurückgegeben werden.
Durch die Fehlerprüfung von APIs kann sichergestellt werden, dass Clients die richtigen Fehlermeldungen erhalten, was die Zuverlässigkeit der Anwendung erhöht.
Verwendung von pytest-Fixtures bei Fehlerprüfungen
Fixtures sind eine leistungsstarke Funktion von pytest, die das Setup und das Aufräumen von Tests effizient verwaltet. Durch die Verwendung von Fixtures bei Fehlerprüfungen kann die Wiederverwendbarkeit und Lesbarkeit des Codes verbessert werden.
Grundlagen von Fixtures
Zuerst werden die Grundlagen von pytest-Fixtures erläutert. Fixtures ermöglichen die Definition gemeinsamer Setup-Aufgaben, die in mehreren Tests verwendet werden können.
import pytest
@pytest.fixture
def sample_data():
return {"name": "test", "value": 42}
def test_sample_data(sample_data):
assert sample_data["name"] == "test"
assert sample_data["value"] == 42
In diesem Beispiel wird ein Fixture sample_data
definiert und in der Testfunktion verwendet.
Verwendung von Fixtures zum Einrichten von APIs
Bei API-Tests können Fixtures verwendet werden, um die Einrichtung des Clients vorzunehmen. Im folgenden Beispiel wird AsyncClient
von httpx als Fixture konfiguriert.
import pytest
from httpx import AsyncClient
from main import app
@pytest.fixture
async def async_client():
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
@pytest.mark.asyncio
async def test_read_item_zero(async_client):
response = await async_client.get("/items/0")
assert response.status_code == 400
assert response.json() == {"detail": "Item ID cannot be zero"}
In diesem Test wird das Fixture async_client
verwendet, um die Einrichtung des Clients vorzunehmen und eine API-Anfrage zu senden.
Effiziente Fehlerprüfungen mit Fixtures
Durch die Kombination von Fixtures und parametrisierten Tests können mehrere Fehlerprüfungen effizient durchgeführt werden.
@pytest.mark.asyncio
@pytest.mark.parametrize("item_id, expected_status, expected_detail", [
(0, 400, "Item ID cannot be zero"),
(-1, 404, "Item not found"),
])
async def test_read_item_errors(async_client, item_id, expected_status, expected_detail):
response = await async_client.get(f"/items/{item_id}")
assert response.status_code == expected_status
assert response.json() == {"detail": expected_detail}
In diesem Beispiel werden das Fixture async_client
und parametrisierte Tests kombiniert, um verschiedene Fehlerfälle kompakt zu beschreiben.
Fixture für Datenbankverbindungen
Wenn Tests eine Datenbankverbindung erfordern, können Fixtures verwendet werden, um die Verbindung einzurichten und aufzuräumen.
import sqlalchemy
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
@pytest.fixture
async def async_db_session():
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
yield session
await engine.dispose()
async def test_db_interaction(async_db_session):
result = await async_db_session.execute("SELECT 1")
assert result.scalar() == 1
In diesem Beispiel wird das Fixture async_db_session
verwendet, um die Verwaltung der Datenbankverbindung durchzuführen und nach dem Test aufzuräumen.
Durch den Einsatz von Fixtures kann der Testcode vereinfacht und die Wartung erleichtert werden. Nutzen Sie pytest-Fixtures, um effiziente Fehlerprüfungen durchzuführen.
Zusammenfassung
Die Verwendung von pytest für Ausnahme- und Fehlerprüfungen ist unerlässlich, um die Qualität der Software zu verbessern. In diesem Artikel haben wir alles von den grundlegenden Einstellungen über Methoden zur Prüfung von Ausnahmen, Überprüfung von Fehlermeldungen, Testen mehrerer Ausnahmebehandlungen, benutzerdefinierte Ausnahmen, Fehlerprüfungen bei APIs bis hin zur Nutzung von Fixtures für effiziente Tests behandelt.
Durch den umfassenden Einsatz der pytest-Funktionen können Fehlerprüfungen effizient und effektiv durchgeführt werden. Dies führt zu einer höheren Zuverlässigkeit und Wartbarkeit des Codes und verhindert Probleme, die durch unerwartete Fehler verursacht werden. Lernen Sie weiterhin Testmethoden mit pytest, um eine qualitativ hochwertige Softwareentwicklung zu gewährleisten.