Testing mit pytest

Neben dem eingebauten Test-Framework von Django wird in der Praxis häufig pytest verwendet. Es ist schlanker, flexibler und lässt sich gut mit Django kombinieren.

Wir nutzen zusätzlich:

  • pytest-django (Integration mit Django)

  • factory_boy (Testdaten erzeugen)

Installation

uv add pytest pytest-django factory-boy

Konfiguration

Eine minimale pytest.ini im Projekt-Root:

[pytest]
DJANGO_SETTINGS_MODULE = event_manager.settings.dev
python_files = tests.py test_*.py *_tests.py

Ordnerstruktur

Tests werden strukturiert im tests-Verzeichnis der App abgelegt:

events/
    tests/
        test_models.py
        test_views.py

Grundidee

Tests sind einfache Funktionen mit dem Präfix test_. pytest übernimmt das Setup automatisch.

Wir verwenden deine Factories:

  • UserFactory

  • CategoryFactory

  • EventFactory

Model-Tests

import pytest
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import timedelta

from events.models import Category, Event
from events.factories import CategoryFactory, EventFactory, UserFactory


@pytest.mark.django_db
def test_create_category_successful():
    category = CategoryFactory()
    assert category.id is not None


@pytest.mark.django_db
def test_category_name_unique():
    CategoryFactory(name="Sports")

    with pytest.raises(Exception):
        CategoryFactory(name="Sports")


@pytest.mark.django_db
def test_event_proper_update():
    event = EventFactory(name="Old Name")
    event.name = "New Name"
    event.full_clean()
    event.save()

    assert Event.objects.get(id=event.pk).name == "New Name"


@pytest.mark.django_db
def test_invalid_event_date_in_past():
    event = EventFactory.build(
        date=timezone.now() - timedelta(days=1)
    )

    with pytest.raises(ValidationError):
        event.full_clean()


@pytest.mark.django_db
def test_invalid_event_name_too_short():
    event = EventFactory.build(name="ab")

    with pytest.raises(ValidationError):
        event.full_clean()

View-Tests

pytest stellt mit client einen Test-Client bereit.

import pytest
from django.urls import reverse

from events.factories import EventFactory, UserFactory


@pytest.mark.django_db
def test_event_delete_as_author(client):
    user = UserFactory()
    event = EventFactory(author=user)

    client.force_login(user)

    url = reverse("events:event-delete", args=[event.slug])
    response = client.post(url)

    assert response.status_code in (200, 302)
    assert not EventFactory._meta.model.objects.filter(id=event.pk).exists()


@pytest.mark.django_db
def test_event_edit_as_unauthorized_user(client):
    author = UserFactory()
    other_user = UserFactory()

    event = EventFactory(author=author)

    client.force_login(other_user)

    url = reverse("events:event-update", args=[event.slug])
    response = client.post(url, {"name": "Hacked Name"})

    assert response.status_code in (403, 302)

Starten der Tests

pytest

oder mit mehr Output:

pytest -v

Good practice

Tests sollten schnell und unabhängig voneinander sein. Nutze Factories statt manuell erzeugter Daten und teste gezielt kritische Logik.