Fabryki Factory Boy w testach Django
Factory Boy to narzędzie zastępującego Django fixtures, czy ręczne tworzenie danych w testach. Ułatwia tworzenie danych potrzebnych do testów, jak i łatwe utrzymanie ich wraz z rozwojem projektu i struktury modeli.
Factory Boya znajdziemy na githubie. Dostępna jest też dokumentacja, czy prezentacja na PyWaw.
Fabryki Factory Boy w testach
Instalacja jest standardowa:Po czym możemy zabrać się za tworzenie fabryk. Można w katalogu aplikacji stworzyć plik factories.py i importować fabryki w pliku z testami (tests.py).
Na potrzeby tego artykułu wykorzystam aplikację użytą w artykule o widokach opartych o klasy. Mamy tam model "Category":
class Category(models.Model):
title = models.CharField(max_length=150)
from demo.models import *
import factory
class CategoryFactory(factory.Factory):
FACTORY_FOR = Category
title = 'abc'
Ważny element to FACTORY_FOR - wskazuje model, którego dotyczy. Kolejna część fabryki to wartości jakie zostaną przypisane do poszczególnych atrybutów modelu. Powyżej tytuł każdej tworzonej kategorii ustawiony zostanie na "abc".
Teraz wykorzystajmy fabrykę do prostego testu. Kategorie są wyświetlane na stronie głównej, więc jeżeli stworzymy jedną to jej tytuł powinien znaleźć się w treści strony głównej. Oto tests.py:
from django.test import TestCase
import demo.factories
class DemoAppCategoryTest(TestCase):
def test_if_category_is_displayed(self):
category = demo.factories.CategoryFactory.create()
response = self.client.get('/')
self.assertTrue(category.title in response.content)
Metoda create tworzy i zwraca obiekt (kategorii). Dalej self.client wykonuje żądanie głównej strony a w assercie sprawdzane jest czy tytuł stworzonej kategorii występuje w zwróconej treści (testy można odpalić za pomocą polecenia python manage.py test NAZWA_APLIKACJI).
Fabryki potrafią więcej. Zazwyczaj dane takie jak nazwa kategorii byłaby unikalna. Dla każdej tworzonej kategorii trzeba by dostarczyć unikalną nazwę/tytuł. Factory Boy ma na to rozwiązanie - sekwencje:
class CategoryFactory(factory.Factory):
FACTORY_FOR = Category
title = factory.Sequence(lambda n: 'Category ' + n)
Pierwsza stworzona kategoria dostanie nazwę "Category 0", druga "Category 1" itd. My piszemy sobie testy, a fabryka odwala czarną robotę unikalności nazwy.
W bardziej życiowych modelach będą jakieś relacje, np model "wpisu":
class Entry(models.Model):
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
user_number = models.IntegerField()
system_number = models.IntegerField()
from django.contrib.auth.models import User
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: 'user' + n)
class EntryFactory(factory.Factory):
FACTORY_FOR = Entry
category = factory.SubFactory(CategoryFactory)
system_number = 1
user_number = 12345
user = factory.SubFactory(UserFactory)
def test_if_entry_is_displayed(self):
entry = demo.factories.EntryFactory.create()
response = self.client.get(reverse('entry-list', kwargs={'category_id': entry.category.id}))
phrase = '<b>user number</b>: %s' % entry.user_number
self.assertTrue(phrase in response.content)
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: 'user' + n)
password = 'abc'
@classmethod
def _prepare(cls, create, **kwargs):
password = kwargs.pop('password', None)
user = super(UserFactory, cls)._prepare(create, **kwargs)
if password:
user.set_password(password)
if create:
user.save()
return user
user = demo.factories.UserFactory.create()
self.client.login(username=user.username, password='abc')
Comment article