Strukturiere robuste Tests mit pytest: Fixture-Injektion, Teardown per yield, tabellengetriebene Parametrisierung und die Fallstricke breiter Scopes.
Diese Lektion in Kodokon öffnenpytest ruht auf zwei starken Ideen: Erkennung per Konvention (Dateien test_*.py, Funktionen test_*) und das schlichte assert, dessen Introspektion bei einem Fehlschlag die tatsächlichen Werte beider Seiten anzeigt - kein Auswendiglernen von assertEqual und seinen dreißig Varianten mehr. Zwei Werkzeuge, die du sofort kennen solltest: pytest.raises, um zu prüfen, dass eine Ausnahme tatsächlich ausgelöst wird, und pytest.approx, um Floats zu vergleichen, ohne gegen die binäre Rundung zu kämpfen.
import pytest
def divide(a: float, b: float) -> float:
return a / b
def test_nominal() -> None:
assert divide(8, 4) == 2.0
def test_zero_division() -> None:
with pytest.raises(ZeroDivisionError):
divide(1, 0)
def test_floats() -> None:
assert 0.1 + 0.2 == pytest.approx(0.3)Fixtures sind die Dependency Injection von pytest: Deklariere einen Parameter, der den Namen einer Fixture trägt, und pytest baut sie und injiziert sie dann. Das yield trennt Setup vom Teardown - der Code danach läuft, selbst wenn der Test fehlschlägt. Der Parameter scope (function standardmäßig, module, session) steuert die Lebensdauer des Objekts. Leg gemeinsame Fixtures in eine conftest.py: Sie werden im ganzen Ordner verfügbar, ohne einen einzigen Import.
import sqlite3
import pytest
@pytest.fixture
def db():
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
yield conn
conn.close()
def test_insert(db) -> None:
db.execute("INSERT INTO users VALUES ('Ada')")
query = "SELECT name FROM users"
rows = db.execute(query).fetchall()
assert rows == [("Ada",)]@pytest.mark.parametrize macht aus einem Test eine Tabelle von Fällen: Jedes Tupel wird zu einem eigenständigen Test, einzeln benannt und filterbar. Staple mehrere parametrize-Dekoratoren, um das kartesische Produkt der Parameter zu erhalten. Gib deinen Fällen lesbare ids über pytest.param(..., id='...') - ein Fehlerbericht sollte verständlich sein, ohne die Quelldatei zu öffnen.
import pytest
def slugify(text: str) -> str:
return "-".join(text.lower().split())
@pytest.mark.parametrize(
("raw", "expected"),
[
("Hello World", "hello-world"),
(" Python rocks ", "python-rocks"),
pytest.param("", "", id="empty"),
],
)
def test_slugify(raw: str, expected: str) -> None:
assert slugify(raw) == expectedpytest -x --lf
pytest -k "slugify and not empty"
pytest -q --durations=5yield steht?pytest --lf?