🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

So schreiben Sie Unit-Tests in Python mit pytest: Vollständiger Leitfaden 2026

⏱️5 min read  ·  971 words

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.

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_valueerror sagt 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.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা