Zurück zur Startseite

Fabriken factory_boy in Django pytest-Tests

Der Artikel demonstriert die Verwendung von factory_boy zur Automatisierung der Erstellung verwandter Django-Objekte in pytest-Tests. Er umfasst SubFactory für ForeignKey, post_generation für ManyToMany und die Integration mit Fixtures. Beispiele decken typische Test-Szenarien für Bibliotheksmodelle ab.

factory_boy: Fabriken für zuverlässige Django-Tests
Advertisement 728x90

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:

Google AdInline article slot
  • 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:

Google AdInline article slot
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:

Google AdInline article slot
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

  • SubFactory erstellt automatisch zugehörige ForeignKey-Objekte bei Bedarf
  • @post_generation behandelt ManyToManyField-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

Advertisement 728x90

Weiterlesen