pytest ist der De-facto-Standard zum Testen von Python-Code im Jahr 2026 – einfacher und leistungsfähiger als der integrierte Unittest. Gute Tests erkennen Fehler vor der Produktion, ermöglichen sicheres Refactoring und dokumentieren, wie sich Ihr Code verhalten sollte. Dieser Leitfaden führt Sie vom ersten Test bis zu fortgeschrittenen Mustern.
📋 Table of Contents
- Einrichtung
- Ihr erster Test
- Das AAA-Muster (Anordnen, Handeln, Durchsetzen)
- Vorrichtungen – Wiederverwendbarer Testaufbau
- Parametrisierung – Viele Fälle testen
- Mocking – Code von Abhängigkeiten isolieren
- Testen von Ausnahmen und Randfällen
- Messung der Abdeckung
- Konfiguration (pytest.ini)
- Best Practices
- Häufig gestellte Fragen
- Fazit
Einrichtung
pip install pytest pytest-cov
# Project structure
myproject/
├── src/
│ └── calculator.py
├── tests/
│ └── test_calculator.py
└── pytest.ini
Ihr erster Test
# src/calculator.py
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# tests/test_calculator.py
from src.calculator import add, divide
import pytest
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_divide():
assert divide(10, 2) == 5
def test_divide_by_zero_raises():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
# Run the tests
pytest # run all tests
pytest -v # verbose (shows each test name)
pytest tests/test_calculator.py::test_add # run one test
pytest -k "divide" # run tests matching "divide"
Das AAA-Muster (Anordnen, Handeln, Durchsetzen)
def test_user_discount():
# Arrange — set up test data
user = User(name="Alice", is_premium=True)
cart = Cart(total=100)
# Act — call the code under test
final_price = apply_discount(cart, user)
# Assert — verify the result
assert final_price == 80 # 20% premium discount
Vorrichtungen – Wiederverwendbarer Testaufbau
import pytest
@pytest.fixture
def sample_user():
# Provides a fresh user for each test that needs one
return User(name="Alice", email="alice@example.com")
@pytest.fixture
def db_connection():
# Setup and teardown - code after yield runs as cleanup
conn = create_test_db()
yield conn # test runs here
conn.close() # cleanup after test
# Tests receive fixtures as arguments
def test_user_email(sample_user):
assert sample_user.email == "alice@example.com"
def test_save_user(sample_user, db_connection):
db_connection.save(sample_user)
assert db_connection.count() == 1
Parametrisierung – Viele Fälle testen
import pytest
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, 200, 300),
])
def test_add_many_cases(a, b, expected):
assert add(a, b) == expected
# Runs 4 separate tests, one per tuple — clear failure reporting per case
Mocking – Code von Abhängigkeiten isolieren
from unittest.mock import patch, MagicMock
# Mock an external API call so tests don't hit the network
def test_fetch_user_data():
with patch('src.api.requests.get') as mock_get:
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
mock_get.return_value.status_code = 200
result = fetch_user_data(1)
assert result["name"] == "Alice"
mock_get.assert_called_once_with("https://api.example.com/users/1")
# Mock with pytest-mock (cleaner)
def test_with_mocker(mocker):
mock_db = mocker.patch('src.service.database')
mock_db.get_user.return_value = {"name": "Bob"}
result = get_user_name(1)
assert result == "Bob"
Testen von Ausnahmen und Randfällen
def test_raises_on_invalid_input():
with pytest.raises(ValueError) as exc_info:
validate_age(-5)
assert "must be positive" in str(exc_info.value)
def test_edge_cases():
assert process([]) == [] # empty input
assert process([1]) == [1] # single item
assert process(None) is None # None handling
Messung der Abdeckung
# Run tests with coverage report
pytest --cov=src --cov-report=term-missing
# Output shows which lines aren't tested:
# Name Stmts Miss Cover Missing
# src/calculator.py 10 1 90% 15
# Generate an HTML report
pytest --cov=src --cov-report=html
# Open htmlcov/index.html to see line-by-line coverage
Konfiguration (pytest.ini)
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = -v --cov=src --cov-report=term-missing
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: integration tests
Best Practices
- Ein Behauptungskonzept pro Test: Jeder Test überprüft ein Verhalten – Fehler deuten direkt auf das Problem hin
- Beschreibende Namen:
test_divide_by_zero_raises_valueerrorsagt Ihnen, was kaputt gegangen ist, ohne den Code zu lesen - Testverhalten, nicht Implementierung: Testen Sie, was die Funktion tut, nicht wie – damit das Refactoring die Tests nicht unterbricht
- Halten Sie Tests schnell und isoliert: Jeder Test sollte unabhängig in beliebiger Reihenfolge ausgeführt werden
- Schein externer Abhängigkeiten: Datenbanken, APIs und Dateisysteme sollten in Unit-Tests
- simuliert werden Streben Sie eine sinnvolle Berichterstattung an: 80 %+ ist ein gutes Ziel, aber kritische Pfade gründlich abdecken, anstatt 100 % anzustreben
Häufig gestellte Fragen
F: Pytest vs. Unittest?
A: Pytest ist einfacher (einfachassert, keine Standardklassen), leistungsfähiger (Fixtures, Parametrisierung) und verfügt über ein umfangreiches Plugin-Ökosystem. Es können auch Unittest-Tests ausgeführt werden. Verwenden Sie Pytest für neue Projekte.
F: Wie viel Testabdeckung benötige ich?
A: 80 % ist ein übliches Ziel. Aber die Abdeckung ist ein Richtwert, kein Ziel – eine 100-prozentige Abdeckung mit schwachen Behauptungen ist schlechter als 80 % mit gründlichen Tests der kritischen Logik. Konzentrieren Sie sich darauf, den Code abzudecken, auf den es ankommt.
F: Soll ich die Datenbank verspotten oder eine Testdatenbank verwenden?
A: Testen Sie es in Unit-Tests (schnell, isoliert). Verwenden Sie bei Integrationstests eine echte Testdatenbank (überprüft, ob tatsächliche Abfragen funktionieren). Beide haben ihren Platz – Unit-Tests für die Logik, Integrationstests für die Datenschicht.
F: Was ist der Unterschied zwischen einer Fixture und einer regulären Funktion?
A: Fixtures werden von Pytest verwaltet – sie werden vor Tests eingerichtet, die sie anfordern, und können danach bereinigt werden (über yield). Sie bieten wiederverwendbare, isolierte Testabhängigkeiten mit automatischem Lebenszyklusmanagement.
F: Wie teste ich asynchronen Code?
A: Installieren Siepytest-asyncio und markieren Sie Tests mit@pytest.mark.asyncio. Dann können Sieawait innerhalb Ihrer Testfunktionen, um asynchrone Funktionen zu testen.
Fazit
pytest macht Python-Tests einfach und leistungsstark. Beginnen Sie mit einfachemassert Anweisungen im AAA-Muster, verwenden Sie Fixtures für wiederverwendbare Setups, parametrisieren Sie, um viele Fälle präzise abzudecken, und simulieren Sie externe Abhängigkeiten, um Tests schnell und isoliert zu halten. Messen Sie die Abdeckung, um ungetesteten Code zu finden, aber geben Sie dem gründlichen Testen kritischer Logik Vorrang vor der Suche nach 100 %. Gute Tests sind eine Investition, die sich jedes Mal auszahlt, wenn Sie mit Zuversicht umgestalten oder Funktionen hinzufügen.
🔗 Share this article
✍️ Leave a Comment