Factory Boy pro automatizaci testů Django modelů
Factory Boy zjednodušuje generování propojených objektů Django v testech. Místo ručního vytváření desítek modelů s vazbami ForeignKey a ManyToMany používají factory knihovnu Faker k automatickému generování realistických dat. To urychluje psaní testů, snižuje duplikaci kódu a minimalizuje chyby při úpravách modelů.
Pro ilustraci použijeme modely knihovny: Genre (žánr), Language (jazyk), Author (autor) a Book (kniha se vztahy).
class Genre(models.Model):
name = models.CharField(max_length=200)
class Language(models.Model):
name = models.CharField(max_length=20)
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
data_of_birth = models.DateField(null=True, blank=True)
data_of_death = models.DateField(null=True, blank=True)
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.ForeignKey('Genre', on_delete=models.CASCADE, null=True)
language = models.ForeignKey('Language', on_delete=models.CASCADE, null=True)
author = models.ManyToManyField('Author')
summary = models.TextField(max_length=1000)
isbn = models.CharField(max_length=13)
Antipattern: Ruční vytváření objektů
Ruční vytváření propojených objektů vede k následujícím problémům:
- Duplikace boilerplate kódu v každém testu
- Hardcoding hodnot místo dynamické generace
- Zamíhání přípravy testovacích dat a logiky kontrol
- Zranitelnost při změnách modelů
Příklad problematického testu:
def test_book_creation():
genre = Genre.objects.create(name="Sci-fi")
language = Language.objects.create(name="Čeština")
author = Author.objects.create(
first_name="Artem",
last_name="Strogackij",
data_of_birth="1925-08-28"
)
book = Book.objects.create(
title="Piknik u cesty",
genre=genre,
language=language,
summary="Jedna z nejznámějších povídek...",
isbn="9785171180975"
)
book.author.add(author)
assert book.display_author() == "Strogackij"
Tento přístup není škálovatelný pro složité scénáře s desítkami propojených objektů.
Základní factory s Fakerem
Factory Boy se bezproblémově integruje s Django i Fakerem pro generování objektů:
class AuthorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Author
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
data_of_birth = factory.Faker("date_time")
data_of_death = factory.Faker("date_time")
Vytvoření autora se tak zredukuje na jediný řádek:
author = AuthorFactory.create()
Stejně jednoduše funguje i pro ostatní modely:
class GenreFactory(factory.django.DjangoModelFactory):
class Meta:
model = Genre
name = factory.Faker("name")
class LanguageFactory(factory.django.DjangoModelFactory):
class Meta:
model = Language
name = factory.Faker("name")
Zpracování propojených modelů
Pro vazby ForeignKey použijte SubFactory:
class BookFactory(factory.django.DjangoModelFactory):
class Meta:
model = Book
title = factory.Faker("sentence")
genre = factory.SubFactory(GenreFactory)
language = factory.SubFactory(LanguageFactory)
summary = factory.Faker("paragraph")
isbn = str(random.randint(1000000000000, 9999999999999))
Pro vazby ManyToMany použijte dekorátor @factory.post_generation:
@factory.post_generation
def author(self, create, extracted, **kwargs):
if not create or not extracted:
return
self.author.add(*extracted)
Test s factory:
def test_book_creation():
author1 = AuthorFactory.create()
author2 = AuthorFactory.create()
book = BookFactory.create(author=[author1, author2])
assert len(book.author.all()) == 2
Integrace s pytest pomocí fixture
V souboru conftest.py vytvořte fixture pro přípravu dat:
@pytest.fixture(scope='function')
def test_book_factory() -> Book:
author1: Author = AuthorFactory.create()
author2: Author = AuthorFactory.create()
book_create: Book = BookFactory.create(author=[author1, author2])
return book_create
Test v test_models.py:
@pytest.mark.django_db
def test_model_book(test_book_factory: Book) -> None:
title: str = test_book_factory.title
genre: Genre = test_book_factory.genre
language: Language = test_book_factory.language
summary: str = test_book_factory.summary
isbn: str = test_book_factory.isbn
authors: QuerySet = test_book_factory.author.all()
assert len(summary) <= 1000
assert len(title) <= 200
assert len(isbn) <= 13
assert isinstance(title, str)
assert isinstance(genre, Genre)
assert isinstance(language, Language)
assert isinstance(summary, str)
assert isinstance(isbn, str)
for author in authors:
assert isinstance(author, Author)
Dekorátor @pytest.mark.django_db vytvoří dočasnou databázi pro izolaci testů.
Klíčové body
- SubFactory automaticky vytvoří propojené objekty pro vazby ForeignKey
- post_generation zpracuje vazby ManyToMany a přijme seznam již existujících objektů
- Fixture pytest oddělují přípravu dat od kontrol
- Faker generuje rozmanitá data, což zvyšuje pokrytí edge-case scénářů
- Úpravy modelů vyžadují úpravy pouze ve factory – nikoli v každém testu
— Editorial Team
Zatím žádné komentáře.