1 # Django "classic" model
2 class Author(models.Model):
3 name = models.CharField(max_length=255)
4 name2 = models.CharField(max_length=255, default='we_dont_care')
5
6 # factory_boy factory definition for that model
7 class AuthorFactory(factory.django.DjangoModelFactory):
8 class Meta:
9 model = Author
10 name = factory.Sequence(lambda n: u'name#%s' % n)
1 class TestObjectCreation(TestCase):
2
3 def test_without_factory_boy(self):
4 author = Author.objects.create(name='George RR Martin')
5 self.assertEqual(author.name, 'George RR Martin')
6
7 def test_with_factory_boy(self):
8 author = AuthorFactory(name='George RR Martin')
9 self.assertEqual(author.name, 'George RR Martin')
10
11 def test_with_factory_boy_auto_creation(self):
12 author = AuthorFactory()
13 self.assertEqual(author.name, 'name#1')
14
15 def test_with_factory_boy_auto_creation_and_undefined_field(self):
16 author = AuthorFactory()
17 self.assertEqual(author.name2, 'we_dont_care')
1 class Author(models.Model):
2 name = models.CharField(max_length=255)
3
4 class Book(models.Model):
5 author = models.ForeignKey(Author)
6 title = models.CharField(max_length=255)
7
8
9 class TestObjectCreation(TestCase):
10
11 def test_without_factory_boy(self):
12 author = Author.objects.create(name="George RR Martin")
13 Book.objects.create(author=author, title="Game of Scones")
14
15 def test_with_factory_boy(self):
16 BookFactory()
1 class Author(models.Model):
2 name = models.CharField(max_length=255)
3
4 class Book(models.Model):
5 author = models.ForeignKey(Author)
6 title = models.CharField(max_length=255)
7
8 # Factory definition
9 class AuthorFactory(factory.django.DjangoModelFactory):
10 class Meta:
11 model = Author
12 name = factory.Sequence(lambda n: u'name#%s' % n)
13
14 class BookFactory(factory.django.DjangoModelFactory):
15 class Meta:
16 model = Book
17 author = factory.SubFactory(AuthorFactory)
18 title = factory.Sequence(lambda n: u'title#%s' % n)
1 class Author(factory.django.DjangoModelFactory):
2 name = models.CharField(max_length=666)
3
4 class Book(models.Model):
5 author = models.ForeignKey(Author)
6 title = models.CharField(max_length=255)
7
8
9 class TestObjectCreation(TestCase):
10
11 def test_without_factory_boy(self):
12 author = Author.objects.create(name="George RR Martin")
13 Book.objects.create(author=author, title="Game of Scones")
14
15 def test_with_factory_boy(self):
16 # Specify nested objects values
17 BookFactory(
18 title="Game of Scones",
19 author__name="George RR Martin"
20 )
We have just added a new_field in Author model that is required
1 class Author(models.Model):
2 name = models.CharField(max_length=255)
3 new_field = models.CharField(max_length=255)
4
5 class Book(models.Model):
6 author = models.ForeignKey(Author)
7 title = models.CharField(max_length=255)
Without factory_boy, we have to update each test that is using Author model
1 class TestAuthor(TestCase):
2
3 def test_author_1(self):
4 Author.objects.create(
5 name = "George RR Martin",
6 new_field = "new value 1")
7
8 def test_author_2(self):
9 Author.objects.create(
10 name = "George RR Martin",
11 new_field = "new value 2")
12 ...
Of course, you can create a setUp class to resolve this problem, but how to deal with several TestCase classes that need Author objects ?
A function that factorise Author creation ? Yep. factory_boy already does the job :-)
With factory_boy, we just have to update the factory.
1 class AuthorFactory(factory.django.DjangoModelFactory):
2 class Meta:
3 model = Author
4 name = factory.Sequence(lambda n: u'author#%s' % n)
5 new_field = factory.Sequence(lambda n: u'new_field#%s' % n)
Each old test is not impacted by the newly added field, and we can use the new_field on new tests.
Example with models that contain more fields
1 class Author(models.Model):
2 name = models.CharField(max_length=255)
3 birth_date = models.DateTimeField()
4 favorite_color = models.CharField(max_length=50)
5 favorite_expression = models.CharField(max_length=1050)
6 favorite_breakfast_cereals = models.CharField(max_length=111)
7
8 class Book(models.Model):
9 author = models.ForeignKey(Author)
10 title = models.CharField(max_length=255)
11 publication_date = models.DateTimeField()
12 category = models.CharField(max_length=255)
Too many useless data impact test readability, and are... useless !
1 class TestBookAuthorFavoriteCereals(TestCase):
2
3 def test_without_factory_boy(self):
4 author = Author.objects.create(
5 name="George RR Martin",
6 birth_date=datetime(1948, 9, 20),
7 favorite_color="black",
8 favorite_expression="Breakfast is coming !",
9 favorite_breakfast_cereals="Honey smacks"
10 )
11 book = Book.objects.create(
12 author = author,
13 title="Game of scones",
14 publication_date=datetime(2014, 9, 9),
15 category="cooking"
16 )
17
18 query = Book.objects.filter(
19 category="cooking",
20 author__favorite_breakfast_cereals="Honey smacks")
21 self.assertEqual(query.count(), 1)
Less infos, and you focus on what is really important for your test.
1 class TestBookAuthorFavoriteCereals(TestCase):
2
3 def test_with_factory_boy(self):
4 BookFactory(
5 category="cooking",
6 author__favorite_breakfast_cereals="Honey smacks"
7 )
8
9 query = Book.objects.filter(
10 category="cooking",
11 author__favorite_breakfast_cereals="Honey smacks")
12 self.assertEqual(query.count(), 1)
1 class Book(models.Model):
2 author = models.ForeignKey(Author)
3
4 class BookFactory(factory.django.DjangoModelFactory):
5 class Meta:
6 model = Book
7 author = factory.SubFactory(AuthorFactory)
Similar to ForeignKey
1 class Book(models.Model):
2 author = models.OneToOneField(Author)
3
4 class BookFactory(factory.django.DjangoModelFactory):
5 class Meta:
6 model = Book
7 author = factory.SubFactory(AuthorFactory)
1 class Author(models.Model):
2 books = models.ManyToManyField(Book)
3
4 class AuthorFactory(factory.django.DjangoModelFactory):
5 class Meta:
6 model = Book
7
8 @factory.post_generation
9 def add_books_to_author(self, create, extracted, **kwargs):
10 if not create:
11 return
12 if extracted:
13 for book in extracted:
14 self.books.add(book)
15
16 # In a test
17 AuthorFactory(add_books_to_author=[book1, book2, book3])
Also possible to manage ManyToManyFields with intermediary tables, GenericForeignKeys, ...
factory.fuzzy module to generate random data for some defined types:
1 class FuzzyFactory(factory.django.DjangoModelFactory):
2 class Meta:
3 model = FuzzyModel
4
5 random_int = factory.fuzzy.FuzzyInteger(30, 99)
6 random_char = factory.fuzzy.FuzzyChoice(['Low', 'Medium', 'High'])
7 [...]
Faker python module can be used to generate "real" data
1 class AuthorFactory(factory.django.DjangoModelFactory):
2 class Meta:
3 model = Author
4
5 name = factory.LazyAttribute(lambda x: faker.name())
6 phone_number = factory.LazyAttribute(lambda x: faker.phone_number())
Faker can generate addresses, datetimes, ...
We can generate some fields depending on other fields
1 class AuthorFactory(factory.Factory):
2 class Meta:
3 model = User
4 name = factory.Sequence(lambda n: 'name%s' % n)
5 email = factory.LazyAttribute(lambda o: '%s@example.com' % o.name)
6
7 class TestLazyAttributes(TestCase):
8
9 def test_lazy(self):
10 author = AuthorFactory()
11 self.assertEqual(author.name, 'name1')
12 self.assertEqual(author.email, 'name1@example.com')
Build: create python object
Create: build + database saving
1 class TestAuthorCreation(TestCase):
2
3 def test_build(self):
4 AuthorFactory.build()
5 self.assertEqual(Author.objects.count(), 0)
6
7 def test_creation(self):
8 # Equivalent to AuthorFactory()
9 AuthorFactory.create()
10 self.assertEqual(Author.objects.count(), 1)
1 class TestBatchAuthorOperations(TestCase):
2
3 def test_build(self):
4 authors = AuthorFactory.build_batch(size=20)
5 self.assertEqual(len(authors), 20)
6 self.assertFalse(Author.objects.exists())
7
8 def test_create(self):
9 AuthorFactory.create_batch(size=20)
10 self.assertEqual(Author.objects.count(), 20)
1 class Author(models.Model):
2 name = models.CharField(max_length=255, unique=True)
3
4 class AuthorFactory(factory.django.DjangoModelFactory):
5 class Meta:
6 model = Author
7 name = factory.Sequence(lambda n: u'author#%s' % n)
8
9 # Imagine you have created an author in an initial data migration
10 # with name='author#1'
11
12 class TestAuthorCreation(TestCase):
13
14 def test_author(self):
15 # IntegrityError because the factory will try to create
16 # an author with name='author#1'
17 AuthorFactory()
Use django_get_or_create
1 class AuthorFactory(factory.django.DjangoModelFactory):
2 class Meta:
3 model = Author
4 django_get_or_create = ('name', )
5 name = factory.Sequence(lambda n: u'author#%s' % n)
6
7 class TestAuthorCreation(TestCase):
8
9 def test_author(self):
10 query = Author.objects.filter(name='author#1')
11 self.assertEqual(query.count(), 1)
12
13 # No more IntegrityError
14 AuthorFactory()
15 self.assertEqual(query.count(), 1)
DON'T create a factory per "context", like BookFactory, BookWithAuthorFactory, BookWithAuthorAndAddressFactory, ...
-> create util functions that uses different factories, depending
on the context.
DON'T use it outside of a test environment:
-> be really careful on random generated fields (!).
DON'T define ALL model fields in your factories, just mandatory ones and without default values:
-> Always better to set data (if default is not wanted) than unset.
1 .
2 +-- manage.py
3 +-- settings
4 +-- your_app
5 | +-- admin.py
6 | +-- factories.py <---
7 | +-- models.py
8 | +-- urls.py
9 | +-- views.py
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |