Django-Modelltests mit factory_boy und pytest
factory_boy-Factories vereinfachen die Erstellung verwandter Django-Modellinstanzen in Tests. Statt manuell Dutzende Modelle mit ForeignKey- und ManyToMany-Beziehungen anzulegen, nutzen Factories Faker, um realistische, vielfältige Testdaten automatisch zu generieren. Dadurch beschleunigt sich das Schreiben von Tests, Redundanzen werden eliminiert und die Wartbarkeit steigt – selbst bei Modellanpassungen bleibt der Testcode stabil.
Nutzen wir als Beispiel eine Bibliotheksdomäne: Genre, Sprache, Autor und Buch (mit entsprechenden Beziehungen).
class Genre(models.Model):
name = models.CharField(max_length=200)
class Sprache(models.Model):
name = models.CharField(max_length=20)
class Autor(models.Model):
vorname = models.CharField(max_length=100)
nachname = models.CharField(max_length=100)
geburtsdatum = models.DateField(null=True, blank=True)
todesdatum = models.DateField(null=True, blank=True)
class Buch(models.Model):
titel = models.CharField(max_length=200)
genre = models.ForeignKey('Genre', on_delete=models.CASCADE, null=True)
sprache = models.ForeignKey('Sprache', on_delete=models.CASCADE, null=True)
autor = models.ManyToManyField('Autor')
zusammenfassung = models.TextField(max_length=1000)
isbn = models.CharField(max_length=13)
Das Anti-Muster: Manuelle Objekterstellung
Das manuelle Instanziieren verwandter Objekte führt zu schwerwiegenden Wartbarkeitsproblemen:
- Wiederholter Boilerplate-Code in jedem Test
- Hartcodierte Werte statt dynamischer, realistischer Datengenerierung
- Unklare Trennung zwischen Testdaten-Setup und Assertions-Logik
- Fragile Tests, die bereits bei kleinsten Modelländerungen brechen
Beispiel für problematischen Code:
def test_buch_erstellung():
genre = Genre.objects.create(name="Science Fiction")
sprache = Sprache.objects.create(name="Russisch")
autor = Autor.objects.create(
vorname="Arkadi",
nachname="Strugazki",
geburtsdatum="1925-08-28"
)
buch = Buch.objects.create(
titel="Picknick am Wegesrand",
genre=genre,
sprache=sprache,
zusammenfassung="Eine der meistgefeierten Novellen...",
isbn="9785171180975"
)
buch.autor.add(autor)
assert buch.anzeigen_autor() == "Strugazki"
Dieser Ansatz skaliert nicht – bei komplexen Szenarien mit Dutzenden miteinander verknüpfter Objekte wird er unübersichtlich und fehleranfällig.
Einfache Factories mit Faker
factory_boy integriert nahtlos mit Django und Faker, um konsistente, realistische Testdaten zu erzeugen:
class AutorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Autor
vorname = factory.Faker("first_name")
nachname = factory.Faker("last_name")
geburtsdatum = factory.Faker("date_time")
todesdatum = factory.Faker("date_time")
Ein Autor lässt sich nun in einer sauberen Zeile erstellen:
autor = AutorFactory.create()
Das gleiche Muster gilt für andere Modelle:
class GenreFactory(factory.django.DjangoModelFactory):
class Meta:
model = Genre
name = factory.Faker("name")
class SpracheFactory(factory.django.DjangoModelFactory):
class Meta:
model = Sprache
name = factory.Faker("name")
Behandlung verwandter Modelle
Für ForeignKey-Felder verwenden Sie SubFactory:
class BuchFactory(factory.django.DjangoModelFactory):
class Meta:
model = Buch
titel = factory.Faker("sentence")
genre = factory.SubFactory(GenreFactory)
sprache = factory.SubFactory(SpracheFactory)
zusammenfassung = factory.Faker("paragraph")
isbn = str(random.randint(1000000000000, 9999999999999))
Für ManyToManyField nutzen Sie @factory.post_generation:
@factory.post_generation
def autor(self, create, extracted, **kwargs):
if not create or not extracted:
return
self.autor.add(*extracted)
Ein sauberer, gut lesbarer Test:
def test_buch_erstellung():
autor1 = AutorFactory.create()
autor2 = AutorFactory.create()
buch = BuchFactory.create(autor=[autor1, autor2])
assert len(buch.autor.all()) == 2
Integration mit pytest über Fixtures
In conftest.py definieren Sie eine wiederverwendbare Fixture:
@pytest.fixture(scope='function')
def test_buch_factory() -> Buch:
autor1: Autor = AutorFactory.create()
autor2: Autor = AutorFactory.create()
buch_erstellen: Buch = BuchFactory.create(autor=[autor1, autor2])
return buch_erstellen
Dann in test_models.py:
@pytest.mark.django_db
def test_modell_buch(test_buch_factory: Buch) -> None:
titel: str = test_buch_factory.titel
genre: Genre = test_buch_factory.genre
sprache: Sprache = test_buch_factory.sprache
zusammenfassung: str = test_buch_factory.zusammenfassung
isbn: str = test_buch_factory.isbn
autoren: QuerySet = test_buch_factory.autor.all()
assert len(zusammenfassung) <= 1000
assert len(titel) <= 200
assert len(isbn) <= 13
assert isinstance(titel, str)
assert isinstance(genre, Genre)
assert isinstance(sprache, Sprache)
assert isinstance(zusammenfassung, str)
assert isinstance(isbn, str)
for autor in autoren:
assert isinstance(autor, Autor)
Der @pytest.mark.django_db-Decorator startet für jeden Test eine isolierte, temporäre Datenbank – so bleibt jeder Test vollständig unabhängig.
Wichtige Erkenntnisse
SubFactoryerstellt automatisch zugehörigeForeignKey-Objekte bei Bedarf@post_generationbehandeltManyToManyField-Beziehungen und akzeptiert eine Liste bereits erstellter Instanzen- pytest-Fixtures trennen sauber Datensetup von Assertions-Logik
- Faker generiert vielfältige, realistische Daten – dadurch verbessert sich die Abdeckung von Randfällen
- Modelländerungen erfordern Anpassungen nur in den Factories – nicht in dutzenden verstreuten Testdateien
— Editorial Team
Noch keine Kommentare.