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

Python-Tests mit Pytest 2026: Fixtures, Mocking und CI-Integration

⏱️5 min read  ·  904 words

pytest ist der Goldstandard für Python-Tests im Jahr 2026. Pytest wird von Django, FastAPI, NumPy und Tausenden von Open-Source-Projekten verwendet und macht das Schreiben und Ausführen von Tests intuitiv und leistungsstark. Dieser vollständige Leitfaden führt Sie vom ersten Test bis hin zu erweiterten Vorrichtungen, Parametrisierung und CI-Integration.

Warum Pytest über Unittest?

  • Einfachere Syntax– schmucklosassertAnweisungen mit hilfreichen Fehlermeldungen
  • Leistungsstarke Vorrichtungen– Abhängigkeitsinjektion mit automatischem Teardown
  • Parametrisieren– Führen Sie einen Test mit vielen Eingaben durch
  • Umfangreiches Plugin-Ökosystem– Abdeckung, xdist, Mock, Asyncio und mehr
  • Bessere Entdeckung– findet Tests automatisch ohne Boilerplate

Installation und erster Test

# Install pytest
pip install pytest pytest-cov pytest-asyncio

# Create a test
# test_math.py
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

# Run tests
pytest                    # run all tests
pytest test_math.py       # specific file
pytest -v                 # verbose output
pytest -k "add"           # run tests matching name
pytest --tb=short         # shorter tracebacks

Testorganisation

# Recommended structure
# tests/
#   conftest.py          — shared fixtures
#   test_users.py
#   test_orders.py
#   integration/
#     test_api.py
#   unit/
#     test_services.py

# pytest.ini or pyproject.toml
# [tool.pytest.ini_options]
# testpaths = ["tests"]
# addopts = "-v --tb=short"

# Test class grouping
class TestUserService:
    def test_create_user(self):
        user = create_user("alice@example.com")
        assert user.email == "alice@example.com"

    def test_create_user_invalid_email(self):
        with pytest.raises(ValueError, match="Invalid email"):
            create_user("not-an-email")

    def test_duplicate_email_raises(self, db):
        create_user("alice@example.com")
        with pytest.raises(DuplicateError):
            create_user("alice@example.com")

Fixtures – die Supermacht von Pytest

import pytest
from app.database import Database
from app.models import User

# Simple fixture
@pytest.fixture
def sample_user():
    return User(id=1, name="Alice", email="alice@example.com")

# Fixture with setup and teardown
@pytest.fixture
def db():
    database = Database(url="sqlite:///:memory:")
    database.create_tables()
    yield database           # test runs here
    database.drop_tables()   # teardown

# Session-scoped fixture — runs once per test session
@pytest.fixture(scope="session")
def app_client():
    from app import create_app
    app = create_app(testing=True)
    return app.test_client()

# Fixture using other fixtures
@pytest.fixture
def admin_user(db):
    user = db.create_user(email="admin@test.com", role="admin")
    return user

# conftest.py — shared fixtures auto-discovered
# pytest finds conftest.py automatically

Parametrisieren – Mehrere Fälle testen

import pytest

def is_valid_email(email: str) -> bool:
    import re
    return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email))

@pytest.mark.parametrize("email,expected", [
    ("alice@example.com", True),
    ("user+tag@domain.co.uk", True),
    ("invalid", False),
    ("no-at-sign", False),
    ("@nodomain.com", False),
    ("user@.com", False),
    ("", False),
])
def test_email_validation(email, expected):
    assert is_valid_email(email) == expected

# Multiple parametrize decorators — creates cartesian product
@pytest.mark.parametrize("base", [2, 10, 16])
@pytest.mark.parametrize("exp", [1, 2, 3])
def test_powers(base, exp):
    result = base ** exp
    assert result == pow(base, exp)

Verspotten mit Pytest-Mock

pip install pytest-mock

import pytest
from app.services import UserService
from app.email import EmailSender

def test_registration_sends_welcome_email(mocker):
    # Mock the email sender
    mock_send = mocker.patch("app.services.EmailSender.send")

    service = UserService()
    service.register("alice@example.com", "securepass")

    # Assert email was sent
    mock_send.assert_called_once_with(
        to="alice@example.com",
        subject="Welcome to TechPulse!"
    )

def test_external_api_timeout(mocker):
    # Mock requests.get to simulate timeout
    mocker.patch(
        "requests.get",
        side_effect=requests.exceptions.Timeout
    )

    with pytest.raises(ServiceUnavailableError):
        fetch_weather("London")

# Spy on real method (calls real code but tracks calls)
def test_cache_used_on_second_call(mocker):
    spy = mocker.spy(cache_service, "get")
    fetch_user(1)
    fetch_user(1)  # should hit cache
    assert spy.call_count == 2

Async-Code testen

pip install pytest-asyncio

import pytest
import pytest_asyncio

# Mark entire file as async
# pytest.ini: asyncio_mode = auto

@pytest.mark.asyncio
async def test_async_fetch():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/api/users")
    assert response.status_code == 200

# Async fixture
@pytest_asyncio.fixture
async def async_db():
    async with AsyncDatabase() as db:
        await db.create_tables()
        yield db
        await db.drop_tables()

Berichterstattung über die Abdeckung

# Run with coverage
pytest --cov=app --cov-report=html --cov-report=term-missing

# Output:
# Name              Stmts   Miss  Cover
# app/models.py        45      3    93%
# app/services.py      87     12    86%
# TOTAL               132     15    89%

# Fail if coverage below threshold
pytest --cov=app --cov-fail-under=80

# .coveragerc or pyproject.toml
# [tool.coverage.run]
# omit = ["*/tests/*", "*/migrations/*"]

pytest Marks – Tests kategorisieren

import pytest

# Define marks in pyproject.toml
# [tool.pytest.ini_options]
# markers = [
#   "slow: marks tests as slow",
#   "integration: marks integration tests",
#   "unit: marks unit tests",
# ]

@pytest.mark.slow
@pytest.mark.integration
def test_full_user_registration_flow(client, db):
    # End-to-end registration test
    response = client.post("/register", json={
        "email": "test@example.com",
        "password": "SecurePass123!"
    })
    assert response.status_code == 201
    assert db.users.count() == 1

# Run only unit tests
# pytest -m unit

# Skip slow tests in CI
# pytest -m "not slow" 

GitHub Actions CI-Integration

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: |
          pip install uv
          uv pip install -e ".[dev]" --system

      - name: Run tests
        run: pytest --cov=app --cov-fail-under=80

      - name: Upload coverage
        uses: codecov/codecov-action@v4

Die Beherrschung von Pytest erfordert Übung, zahlt sich aber aus. Schreiben Sie Tests neben Features (TDD), verwenden Sie Fixtures zum Auf- und Abbau, parametrisieren Sie für Randfälle und simulieren Sie externe Abhängigkeiten. Streben Sie eine Abdeckung von über 80 % der Geschäftslogik und 100 % der kritischen Pfade an.

✍️ Leave a Comment

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

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