Fixtures & Factory Boy: Testdaten sauber aufbauen
Ein häufiger Grund, warum Testsuiten unwartbar werden: handgeschriebene Testdaten, die in jedem Test wiederholt und bei jeder Model-Änderung angefasst werden müssen. pytest-Fixtures und Factory Boy lösen das elegant.
Das Testdaten-Problem
# Mühsam und fehleranfällig - in jedem Test wiederholt
def test_etwas():
user = User.objects.create_user("u1", "u1@x.de", "pw")
cat = Category.objects.create(name="Tech")
article = Article.objects.create(
title="T", author=user, category=cat, status="published"
)
# ... eigentlicher Test erst hierpytest-Fixtures: wiederverwendbare Bausteine
# conftest.py
import pytest
from django.contrib.auth import get_user_model
@pytest.fixture
def user(db):
return get_user_model().objects.create_user("tester", password="pw")
@pytest.fixture
def category(db):
return Category.objects.create(name="Tech")
# Fixtures können aufeinander aufbauen
@pytest.fixture
def article(db, user, category):
return Article.objects.create(
title="Test", author=user, category=category, status="published"
)# Test bekommt Fixtures per Parameter - sauber und lesbar
def test_artikel_sichtbar(client, article):
response = client.get(f"/articles/{article.slug}/")
assert response.status_code == 200Fixture-Scopes: einmal vs. pro Test
Fixtures können unterschiedlich lange leben — das spart Zeit bei teuren Aufbauten:
@pytest.fixture(scope="function") # Standard: pro Test neu
def frischer_user(db): ...
@pytest.fixture(scope="session") # einmal für den ganzen Lauf
def api_config(): return load_config()
@pytest.fixture(scope="module") # einmal pro Testdatei
def teure_ressource(): ...Factory Boy: Testdaten als Fabriken
Factory Boy erzeugt Objekte mit sinnvollen Defaults — du gibst nur an, was für den Test wirklich relevant ist:
# factories.py
import factory
from factory.django import DjangoModelFactory
class UserFactory(DjangoModelFactory):
class Meta:
model = "auth.User"
username = factory.Sequence(lambda n: f"user_{n}")
email = factory.LazyAttribute(lambda o: f"{o.username}@example.com")
class ArticleFactory(DjangoModelFactory):
class Meta:
model = Article
title = factory.Faker("sentence", locale="de_DE")
status = "draft"# Im Test: nur das Relevante angeben, Rest kommt automatisch
def test_veroeffentlicht(db):
a = ArticleFactory(status="published") # title etc. automatisch
assert a.status == "published"
# Mehrere auf einmal
def test_liste(db):
ArticleFactory.create_batch(5, status="published")
assert Article.objects.filter(status="published").count() == 5Relationen & SubFactory
class ArticleFactory(DjangoModelFactory):
class Meta:
model = Article
title = factory.Faker("sentence")
# Verknüpfte Objekte automatisch miterzeugen:
author = factory.SubFactory(UserFactory)
category = factory.SubFactory(CategoryFactory)
# ArticleFactory() erzeugt jetzt automatisch User + Category mitTraits & Sequenzen
Traits bündeln Varianten, Sequenzen sorgen für eindeutige Werte:
class ArticleFactory(DjangoModelFactory):
class Meta:
model = Article
title = factory.Sequence(lambda n: f"Artikel {n}")
status = "draft"
class Params:
veroeffentlicht = factory.Trait(
status="published",
published_at=factory.LazyFunction(timezone.now),
)
# Nutzung:
ArticleFactory(veroeffentlicht=True) # setzt status + published_atFazit
Gute Testdaten sind wiederverwendbar und sprechend. pytest-Fixtures kapseln den Aufbau, Factory Boy liefert sinnvolle Defaults und macht klar, was ein Test wirklich braucht. Zusammen ergeben sie Tests, die man auch in einem Jahr noch versteht — und die eine Model-Änderung nicht reihenweise brechen lässt.
