← Zurück zum Blog
DjangopytestTesting18. April 2026· 13 min Lesezeit

Django Testing mit pytest: Testabdeckung professionell aufbauen

Inhalt
  1. Setup: pytest-django konfigurieren
  2. Fixtures und Factory Boy
  3. Unit Tests für Models und Services
  4. API-Tests mit DRF
  5. Mocking und Patching
  6. Coverage und CI-Integration

In meinen Projekten ist eine gute Testsuite der Unterschied zwischen mutigem und ängstiglichem Refactoring. Hier zeige ich den kompletten Stack den ich nutze.

Setup: pytest-django konfigurieren

pip install pytest pytest-django pytest-cov factory_boy

# pytest.ini oder pyproject.toml
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings.test
python_files = tests.py test_*.py *_tests.py
addopts = --reuse-db -v  # --reuse-db: DB nicht nach jedem Run löschen

# settings/test.py
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": ":memory:",  # In-Memory für maximale Geschwindigkeit
    }
}
CELERY_TASK_ALWAYS_EAGER = True  # Celery synchron ausführen

Fixtures und Factory Boy

# factories.py
import factory
from factory.django import DjangoModelFactory
from django.contrib.auth import get_user_model
from myapp.models import Article, Category

class UserFactory(DjangoModelFactory):
    class Meta:
        model = get_user_model()

    username = factory.Sequence(lambda n: f"user_{n}")
    email = factory.LazyAttribute(lambda o: f"{o.username}@example.com")
    password = factory.PostGenerationMethodCall("set_password", "password123")

class CategoryFactory(DjangoModelFactory):
    class Meta:
        model = Category
    name = factory.Sequence(lambda n: f"Kategorie {n}")

class ArticleFactory(DjangoModelFactory):
    class Meta:
        model = Article
    title  = factory.Faker("sentence", nb_words=6, locale="de_DE")
    author = factory.SubFactory(UserFactory)
    category = factory.SubFactory(CategoryFactory)
    status = "draft"
# Fixtures als pytest-Fixtures
import pytest
from .factories import UserFactory, ArticleFactory

@pytest.fixture
def user(db):
    return UserFactory()

@pytest.fixture
def article(db, user):
    return ArticleFactory(author=user, status="published")

Unit Tests für Models und Services

# test_models.py
import pytest
from .factories import ArticleFactory

@pytest.mark.django_db
class TestArticleModel:
    def test_str_representation(self):
        article = ArticleFactory(title="Mein Artikel")
        assert str(article) == "Mein Artikel"

    def test_publish_sets_status(self):
        article = ArticleFactory(status="draft")
        article.publish()
        assert article.status == "published"
        assert article.published_at is not None

    def test_unpublished_articles_excluded_from_qs(self):
        ArticleFactory(status="published")
        ArticleFactory(status="draft")
        assert Article.objects.published().count() == 1

API-Tests mit DRF

# test_views.py
import pytest
from rest_framework.test import APIClient
from .factories import UserFactory, ArticleFactory

@pytest.fixture
def api_client():
    return APIClient()

@pytest.fixture
def auth_client(api_client, user):
    api_client.force_authenticate(user=user)
    return api_client

@pytest.mark.django_db
class TestArticleAPI:
    def test_list_returns_200(self, api_client):
        ArticleFactory.create_batch(3, status="published")
        response = api_client.get("/api/articles/")
        assert response.status_code == 200
        assert len(response.data["results"]) == 3

    def test_create_requires_auth(self, api_client):
        response = api_client.post("/api/articles/", {"title": "Test"})
        assert response.status_code == 401

    def test_create_article(self, auth_client):
        data = {"title": "Neuer Artikel", "status": "draft"}
        response = auth_client.post("/api/articles/", data)
        assert response.status_code == 201
        assert response.data["title"] == "Neuer Artikel"

Mocking und Patching

from unittest.mock import patch, MagicMock

@pytest.mark.django_db
def test_email_sent_on_publish(article):
    with patch("myapp.tasks.send_publication_email.delay") as mock_email:
        article.publish()
        mock_email.assert_called_once_with(article.id)

@pytest.mark.django_db
def test_external_api_call():
    with patch("myapp.services.requests.get") as mock_get:
        mock_get.return_value = MagicMock(
            status_code=200,
            json=lambda: {"data": "test"}
        )
        result = fetch_external_data()
    assert result == {"data": "test"}

Coverage und CI-Integration

# Coverage-Report generieren
pytest --cov=myapp --cov-report=html --cov-report=term-missing

# .coveragerc
[run]
omit =
    */migrations/*
    */tests/*
    manage.py
    */settings/*

[report]
fail_under = 80  # Test schlaägt fehl unter 80% Coverage
Yevhen Chubchyk
Yevhen Chubchyk
Senior Python / Django Entwickler · Freelancer seit 2016 · 20+ Jahre IT-Erfahrung. Kunden: Siemens Healthineers, Engie, natureOffice u.a.