Fabryki factory_boy do automatyzacji testów modeli Django
Fabryki oparte na bibliotece factory_boy znacznie ułatwiają generowanie powiązanych obiektów Django w testach. Zamiast ręcznego tworzenia dziesiątek modeli z relacjami ForeignKey i ManyToMany, fabryki wykorzystują Faker do automatycznego generowania realistycznych danych. Dzięki temu szybciej piszesz testy, unikasz powtórzeń kodu i minimalizujesz błędy przy zmianach w modelach.
Przyjrzymy się modelom biblioteki: Genre (gatunek), Language (język), Author (autor) oraz Book (książka z relacjami).
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)
Antywzorzec ręcznego tworzenia obiektów
Ręczne tworzenie powiązanych obiektów prowadzi do wielu problemów:
- Powielanie kodu szkieletowego w każdym teście
- Twarde zakodowanie wartości zamiast dynamicznego generowania danych
- Mieszanie przygotowania danych z logiką asercji
- Łamliwość testów przy zmianach w definicjach modeli
Przykład problematycznego testu:
def test_book_creation():
genre = Genre.objects.create(name="Fantastyka")
language = Language.objects.create(name="Polski")
author = Author.objects.create(
first_name="Arkadij",
last_name="Strugacki",
data_of_birth="1925-08-28"
)
book = Book.objects.create(
title="Piknik na skraju drogi",
genre=genre,
language=language,
summary="Jedna z najbardziej znanych nowel...",
isbn="9785171180975"
)
book.author.add(author)
assert book.display_author() == "Strugacki"
Takie podejście nie skaluje się w złożonych scenariuszach z dziesiątkami powiązanych obiektów.
Podstawowe fabryki z Fakerem
Factory_boy integruje się z Django i Fakerem, umożliwiając generowanie obiektów:
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")
Tworzenie autora sprowadza się do jednej linii:
author = AuthorFactory.create()
Analogicznie dla innych modeli:
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")
Obsługa powiązanych modeli
Dla relacji ForeignKey używamy 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))
Dla relacji ManyToMany — dekorator @factory.post_generation:
@factory.post_generation
def author(self, create, extracted, **kwargs):
if not create or not extracted:
return
self.author.add(*extracted)
Test z wykorzystaniem fabryk:
def test_book_creation():
author1 = AuthorFactory.create()
author2 = AuthorFactory.create()
book = BookFactory.create(author=[author1, author2])
assert len(book.author.all()) == 2
Integracja z pytest poprzez fikstury
W pliku conftest.py tworzymy fiksturę do przygotowania danych:
@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 w 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)
Dekorator @pytest.mark.django_db tworzy tymczasową bazę danych, zapewniając izolację testów.
Kluczowe punkty
- SubFactory automatycznie tworzy powiązane obiekty typu ForeignKey
- post_generation obsługuje relacje ManyToMany, przyjmując listę gotowych obiektów
- Fikstury pytest oddzielają przygotowanie danych od logiki sprawdzania
- Faker generuje różnorodne dane, co zwiększa pokrycie przypadków brzegowych
- Zmiany w modelach wymagają modyfikacji tylko w fabrykach — nie w całej bazie testów
— Editorial Team
Brak komentarzy.