pytest ist das verbreitetste Testframework für Python. Es findet Tests automatisch, braucht kaum Boilerplate und liefert bei Fehlern verständliche Ausgaben. Diese Referenz zeigt Aufrufe, Selektoren, Fixtures und Ausgabeoptionen.

Aufruf und Sammlung

pytest                          # alles ab dem aktuellen Verzeichnis
pytest tests/                   # nur ein Verzeichnis
pytest tests/test_api.py        # einzelne Datei
pytest tests/test_api.py::test_login            # einzelne Funktion
pytest tests/test_api.py::TestKunde::test_neu   # einzelne Methode

# Nur sammeln, nicht ausführen
pytest --collect-only
pytest --co -q

Standardmäßig sammelt pytest Dateien, die test_*.py oder *_test.py heißen, sowie Funktionen und Klassen mit dem Präfix test.

Auswahl von Tests

# Nach Namen filtern (Teilstring-Abgleich)
pytest -k "login and not slow"
pytest -k "api or web"

# Nach Markern
pytest -m slow
pytest -m "integration and not flaky"
pytest --markers                      # vorhandene Marker anzeigen

# Fehlgeschlagenen Test wiederholen
pytest --last-failed              # nur die zuletzt fehlgeschlagenen
pytest --failed-first             # zuerst fehlgeschlagene

Ausgabe steuern

-v, --verbose        jeder Test einzeln
-q, --quiet          knappe Ausgabe
-s                   Ausgaben (print) nicht abfangen
-x                   beim ersten Fehler abbrechen
--maxfail=3          nach drei Fehlern abbrechen
--tb=short|long|line|no  Fehlerdarstellung
-rA                  Zusammenfassung aller Ergebnisse
--durations=10       die zehn langsamsten Tests
--color=yes          farbige Ausgabe erzwingen

Tests schreiben

import pytest

def test_addition():
    assert 1 + 1 == 2

def test_ausnahme():
    with pytest.raises(ValueError, match="ungültig"):
        int("keine zahl")

@pytest.mark.parametrize("eingabe,erwartet", [
    ("1", 1),
    ("-2", -2),
    ("007", 7),
])
def test_konvertierung(eingabe, erwartet):
    assert int(eingabe) == erwartet

@pytest.mark.slow
def test_lange_operation():
    assert langsame_funktion() is True

Fixtures

@pytest.fixture
def db():
    verbindung = verbinde()
    yield verbindung          # alles vor yield ist Aufbau, danach Abbau
    verbindung.close()

@pytest.fixture(scope="session")
def client():
    return starte_testclient()

def test_abfrage(db):
    assert db.query("SELECT 1") == 1

def test_mit_client(client):
    assert client.get("/health").status_code == 200

Die Scopes steuern die Lebensdauer: function (Standard) für jeden Test neu, class, module, package und session für längere Laufzeiten. Ein Session-Fixture ist besonders nützlich für teure Aufbauten wie Datenbank oder Testserver.

Konfiguration

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers --cov=app"
markers = [
  "slow: langlaufende Tests",
  "integration: braucht echte Dienste",
]

# Alternativ pytest.ini
[pytest]
minversion = 8.0
testpaths = tests
filterwarnings = error

--strict-markers ist eine sehr sinnvolle Einstellung: Ein Tippfehler in einem Marker wird dann zum Fehler statt still ignoriert – sonst wundert man sich, warum -m integration nichts auswählt.

Berichte und Abdeckung

pytest --junitxml=bericht.xml          # für CI-Systeme
pytest --cov=app --cov-report=term-missing
pytest --cov=app --cov-report=html:abdeckung/
pytest -n auto                          # parallel (pytest-xdist)
pytest --random-order                   # Reihenfolge variieren (Plugin)

-n auto verteilt Tests auf mehrere Prozesse und verkürzt lange Läufe erheblich. Voraussetzung ist, dass Tests voneinander unabhängig sind – und genau das deckt der parallele Lauf gnadenlos auf, wenn sie es nicht sind.

Praxis-Tipps

  • Ein Test, ein Grund: Ein Test, der fünf Dinge prüft, ist bei einem Fehler schwer zu deuten.
  • --strict-markers und filterwarnings = error: Beide Einstellungen verwandeln Nachlässigkeit in sichtbare Fehler.
  • Fixtures statt Wiederholung: Aufbau und Abbau gehören in Fixtures, nicht in jede Testfunktion.
  • Testdauer im Blick behalten: --durations=10 deckt regelmäßig unerwartet langsame Tests auf – meist fehlt ein Loop, ein Timeout oder eine echte Abhängigkeit.

Verwandte Grundlagen: Python-Befehle, Unit-Test, Testdaten, CI/CD.