Unit-Tests in Python mit pytest: Deinen Code automatisch absichern
Manuelles Ausprobieren reicht irgendwann nicht mehr. Mit pytest schreibst du automatisierte Tests, die deinen Code bei jeder Änderung prüfen – wir starten bei Null und arbeiten uns zu Fixtures und Parametrisierung vor.
Du hast eine Funktion geschrieben, sie ein paarmal von Hand ausprobiert, alles sah gut aus – und drei Wochen später hat eine kleine Änderung sie unbemerkt kaputt gemacht. Genau dieses Problem lösen automatisierte Tests: Sie prüfen deinen Code bei jeder Änderung in Sekunden, ganz ohne manuelles Klicken. In diesem Beitrag lernst du pytest kennen, das beliebteste Test-Framework für Python. Wir starten bei Null und arbeiten uns bis zu Fixtures und Parametrisierung vor.
Warum automatisierte Tests?
Tests sind kein bürokratischer Zusatzaufwand, sondern ein Sicherheitsnetz. Sie geben dir die Freiheit, deinen Code umzubauen (Refactoring), ohne Angst, dass du dabei heimlich etwas zerstörst. Läuft die Testsuite grün durch, funktioniert das geprüfte Verhalten noch. Ein guter Test dokumentiert außerdem, wie eine Funktion sich verhalten soll – oft verständlicher als jeder Kommentar. Und wer beim Bewerben ein Projekt mit Tests vorzeigt, signalisiert sofort Professionalität.
Python bringt mit unittest zwar ein eigenes Framework mit, doch pytest ist deutlich schlanker und angenehmer zu schreiben. Du brauchst keine Klassen und kein umständliches Vokabular – ein einfaches assert genügt.
pytest installieren und der erste Test
Installiert wird pytest wie jedes andere Paket über pip. Am besten tust du das in einer virtuellen Umgebung, damit dein Projekt sauber bleibt.
pip install pytest
# Version prüfen
pytest --versionNehmen wir an, du hast eine Datei rechner.py mit einer simplen Funktion:
# rechner.py
def addiere(a, b):
return a + bDie Tests kommen in eine eigene Datei, deren Name mit test_ beginnt – daran erkennt pytest sie automatisch. Auch die Testfunktionen selbst beginnen mit test_.
# test_rechner.py
from rechner import addiere
def test_addiere_positive_zahlen():
assert addiere(2, 3) == 5
def test_addiere_negative_zahlen():
assert addiere(-1, -4) == -5Ausgeführt wird alles mit einem einzigen Befehl im Projektordner:
pytestpytest sucht selbstständig nach allen test_*.py-Dateien, führt die Testfunktionen aus und zeigt dir eine übersichtliche Zusammenfassung: ein grünes 2 passed, wenn alles stimmt, oder einen roten Fehlerbericht mit der genauen Zeile, wenn nicht.
Assertions: Das Herzstück jedes Tests
Der Kern eines Tests ist immer eine Behauptung mit dem Schlüsselwort assert: „Ich behaupte, dieser Ausdruck ist wahr." Ist er es nicht, schlägt der Test fehl. Der große Vorteil von pytest: Es analysiert die fehlgeschlagene Behauptung und zeigt dir die tatsächlichen Werte an.
def test_beispiele():
assert "Python".lower() == "python"
assert 10 > 3
assert "code" in "meine code reise"
assert len([1, 2, 3]) == 3Scheitert etwa assert addiere(2, 2) == 5, meldet pytest nicht nur „fehlgeschlagen", sondern zeigt assert 4 == 5. Du siehst also sofort, dass das Ergebnis 4 war und nicht die erwarteten 5 – kein Rätselraten nötig.
Ausnahmen testen mit pytest.raises
Guter Code prüft nicht nur den Normalfall, sondern auch die Fehlerfälle. Manchmal soll eine Funktion eine Ausnahme werfen – etwa bei einer Division durch Null. Um zu testen, dass das wirklich passiert, nutzt du den Context Manager pytest.raises.
import pytest
def teile(a, b):
if b == 0:
raise ValueError("Division durch Null ist nicht erlaubt")
return a / b
def test_teile_normal():
assert teile(10, 2) == 5
def test_teile_durch_null():
with pytest.raises(ValueError):
teile(10, 0)Der Test test_teile_durch_null gilt nur dann als bestanden, wenn innerhalb des with-Blocks tatsächlich ein ValueError geworfen wird. Bleibt er aus, schlägt der Test fehl. So stellst du sicher, dass deine Funktion sich auch bei falscher Eingabe korrekt verhält.
Parametrisierung: Viele Fälle, ein Test
Oft willst du dieselbe Logik mit vielen verschiedenen Eingaben prüfen. Statt für jeden Fall eine eigene Funktion zu schreiben, gibt es @pytest.mark.parametrize. Du gibst eine Liste von Wertepaaren an, und pytest führt den Test für jedes Paar einzeln aus.
import pytest
def ist_gerade(n):
return n % 2 == 0
@pytest.mark.parametrize("eingabe, erwartet", [
(2, True),
(3, False),
(0, True),
(-4, True),
(7, False),
])
def test_ist_gerade(eingabe, erwartet):
assert ist_gerade(eingabe) == erwartetpytest meldet hier 5 passed – jeder Fall zählt als eigener Test. Schlägt einer fehl, siehst du genau, welche Eingabe das Problem verursacht hat. Das hält deine Tests kurz und deckt trotzdem viele Randfälle ab.
Fixtures: Vorbereitung wiederverwenden
Manchmal brauchen mehrere Tests dieselbe Ausgangssituation – etwa ein vorbereitetes Objekt oder Testdaten. Dafür gibt es Fixtures. Eine mit @pytest.fixture markierte Funktion liefert Daten, die du einfach als Argument in deine Testfunktion hineinreichst. pytest ruft die Fixture automatisch auf.
import pytest
@pytest.fixture
def beispiel_nutzer():
return {"name": "Anton", "rollen": ["admin", "autor"]}
def test_nutzer_name(beispiel_nutzer):
assert beispiel_nutzer["name"] == "Anton"
def test_nutzer_ist_admin(beispiel_nutzer):
assert "admin" in beispiel_nutzer["rollen"]Beide Tests bekommen frische Testdaten, ohne dass du sie doppelt schreiben musst. Fixtures können auch aufwendigere Dinge aufbauen – eine Datenbankverbindung, eine temporäre Datei – und diese nach dem Test wieder sauber abräumen.
Fazit
Mit pytest hast du in wenigen Minuten deine erste Testsuite am Laufen: Test-Dateien mit test_ beginnen, mit assert Behauptungen aufstellen, pytest.raises für Fehlerfälle nutzen und mit parametrize sowie Fixtures den Code kurz halten. Fang klein an – schreibe für deine nächste Funktion direkt zwei, drei Tests. Du wirst schnell merken, wie viel entspannter du Code änderst, wenn ein grünes Testergebnis dir bestätigt, dass noch alles funktioniert. Tests sind keine Kür für Profis, sondern eine Gewohnheit, die dich als Entwickler von Anfang an weiterbringt.