factory_boy - Introduction

  • Remplacement des fixtures de Django
  • Simplifier l'écriture de tests
  • Charger des jeux de données
  • Proche du modèle
$ pip install factory_boy

Presenter Notes

Données de tests

class HandWrittenTestCase(TestCase):
    def setUp(self):
        self.john = models.Person.objects.create(
            first_name='John", last_name="Lennon")

        self.address = models.Address.objects.create(
            person=self.john,
            street="42 rue du Blah",
            city="Chombreizh-les-Congs",
        )


class FixtureTestCase(TestCase):
    fixtures = 'beatles.yaml'

Presenter Notes

Via des fixtures

- model: myapp.person
  pk: 1
  fields:
    first_name: John
    last_name: Lennon
- model: myapp.address
  pk: 1
  fields:
    person: 1
    street: 42 rue du Blah
    city: Chombier-les-Congs

Presenter Notes

Génération côté Python

person = models.Person.objects.create(
    first_name=u"John",
    last_name="Lennon",
)
address = models.Address.objects.create(
    person=person,
    street=u"42 rue du Blah",
    city=u"Chombier-Lès-Congs",
)

Presenter Notes

Évolution du modèle

Ajout d'un champ

  • Modifier tous les .yaml
  • Repasser sur tous tests

Ajout d'une ForeignKey

  • Import des .yaml dans une base vierge
  • Export (sans mélanger les applications)
  • Ajout de code sur tous les tests

Presenter Notes

Avec factory_boy

class PersonFactory(factory.Factory):
    first_name = factory.Iterator([
        "John", "Paul", "George", "Ringo"])

    last_name = factory.Iterator([
        "Lennon", "McCartney", "Harrison", "Starr"])

class AddressFactory(factory.Factory):
    FACTORY_FOR = models.Address

    person = factory.SubFactory(PersonFactory)
    street = factory.Sequence(lambda n: "%s rue du Blah" % n)
    city = "Chombier-les-Congs"

Presenter Notes

Appel d'une Factory

>>> address = AddressFactory()
>>> address.person
<Person: John Lennon>
>>> address
<Address: 1 rue du Blah, Chombier-les-Congs>

>>> address2 = AddressFactory()
>>> address2.person
<Person: Paul McCartney>
>>> address2
<Address: 2 rue du Blah, Chombier-les-Congs>

Presenter Notes

Forcer la valeur d'un attribut

>>> address = AddressFactory(city="Rennes")
>>> address
<Address: 3 rue du Blah, Rennes>

>>> address = AddressFactory(person__first_name="Ford")
>>> address.person
<Person: Ford Harrison>

Presenter Notes

Champs dynamiques

class PersonFactory(factory.Factory):
    # first_name = ...

    username = factory.LazyAttribute(
        lambda p: p.first_name.lower()[0] + p.last_name.lower())
>>> ringo = PersonFactory()
>>> ringo.username
'rstarr'

>>> django = PersonFactory(first_name="Django",
...     last_name="Reinhardt")
>>> django.username
'dreinhardt'

Presenter Notes

Champs dynamiques

class PersonFactory(factory.Factory):
    # first_name = ...

    @factory.lazy_attribute
    def email(self):
        return '%s.%s@example.com' % (
            self.first_name.lower(),
            self.last_name.lower())
>>> ringo = PersonFactory()
>>> ringo.email
'ringo.starr@example.com'

>>> john = PersonFactory(last_name='Doe')
>>> john.email
'john.doe@example.com'

Presenter Notes

Champs liés

class Profile(models.Model):
    user = models.ForeignKey(auth_models.User)
    # ...

class ProfileFactory(factory.Factory):
    FACTORY_FOR = models.Profile
    phone = factory.Sequence(lambda n: '01%08d' % n)

class UserFactory(base_factories.UserFactory):
    profile = factory.RelatedFactory(ProfileFactory, name='user')
>>> user = UserFactory(profile__phone='012345')
>>> user.profile
<Profile for John Doe>
>>> user.profile.phone
'012345'

Presenter Notes

Méthodes utiles

>>> users = UserFactory.create_batch(size=42)
[<User: John Doe1>, <User: John Doe2>, ...]


>>> address_factory = factory.make_factory(Address,
...     street=factory.Sequence(
...               lambda n: "%d rue des brehons" % n),
...     city="Rennes")
>>> address_factory()
<Address: 1 rue des brehons, Rennes>

Presenter Notes

Déclarations

class SomeFactory(factory.Factory):

    other = factory.SubFactory(OtherFactory, foo=1)
>>> obj = SomeFactory(other__foo=2)
>>> obj.other.foo
2
class UserFactory(factory.Factory):
    address = factory.SubFactory(AddressFactory)
    language = factory.SelfAttribute(
        'address.country.main_language')

Presenter Notes

Pour Django

class MyModelFactory(factory.DjangoModelFactory):
    FACTORY_FOR = MyModel
>>> obj = MyModelFactory.build()
>>> obj.pk
None

>>> obj = MyModelFactory()
>>> obj.pk
1

Presenter Notes

Usage avancé

class OrderFactory(factory.Factory):
    FACTORY_FOR = models.Order

    delivery_address = factory.SubFactory(AddressFactory)
    user = factory.SubFactory(UserFactory)

Comment partager l'adresse entre Order.delivery_address et Order.user.default_address ?

class OrderFactory(factory.Factory):
    user = factory.SubFactory(UserFactory,
        default_address=factory.SelfAttribute(
            '..delivery_address'),
        )

Presenter Notes

Usage avancé

class UserFactory(factory.Factory):
    # ...
    @classmethod
    def _prepare(cls, create, **kwargs):
        # Extract the provided password, if any
        password = kwargs.pop('password', None)
        # Usual process
        user = super(UserFactory, cls)._prepare(create, **kwargs)

        if password is not None:
            # Set the password
            user.set_password(password)
            if create:
                # Object is expected to be saved to database
                user.save()
        return user

Presenter Notes

Conclusion

Liens

Roadmap

  • get_or_create et résolution des conflits sur champs uniques
  • Documentation
  • Nouvelles déclarations d'attributs
  • Python3

Presenter Notes