.. _pytest: .. index:: single: pytest single: Django; pytest single: Factory Boy single: Test-Client single: View Tests 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 ============= .. code-block:: bash uv add pytest pytest-django factory-boy Konfiguration =============== Eine minimale ``pytest.ini`` im Projekt-Root: .. code-block:: ini [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: .. code-block:: text 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 ============ .. code-block:: python 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. .. code-block:: python 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 ================== .. code-block:: bash pytest oder mit mehr Output: .. code-block:: bash pytest -v .. admonition:: Good practice Tests sollten schnell und unabhängig voneinander sein. Nutze Factories statt manuell erzeugter Daten und teste gezielt kritische Logik.