<style> .reveal { font-size: 32px; } .footer { display: none; } .reveal section .flat img { border: none; box-shadow: none; background: transparent; } .reveal table { font-size: 0.8em; } </style> ### Générer des données pour vos tests ##### Avec factory_boy --- ### Qui suis-je ? - Développeur python - Curieux de tout ce qui touche aux tests et l'intégration continue - Mainteneur du projet Funkwhale <div class="flat"> ![](https://funkwhale.audio/img/logos/with-text.svg) </div> --- ### Pourquoi écrire des tests ? - Éviter les régressions - Développer plus sereinement - Écrire un code de meilleure qualité --- ### Pourquoi *ne pas* écrire des tests ? - C'est complexe - C'est du travail en plus - C'est pénible --- ### Le plus difficile… C'est souvent de mettre l'application dans un état adapté pour pouvoir tester. Quelles solutions existent ? --- ### Les fixtures Une donnée, un objet ou une ressource créés avant un test, qui permettent son bon déroulement. --- ### Exemple de fixture (1/2) ```python class MyTestCase(unittest.TestCase): def setUp(self): self.user = User( username='test', email='contact@user.test' ) self.user.save() ``` --- ### Exemple de fixture (2/2) ```python class MyTestCase(unittest.TestCase): def setUp(self): # … def test_not_enforced_2FA_for_users(self): assert self.user.enforce_2FA is False def test_enforced_2FA_for_staff(self): self.user.is_staff = True self.user.save() assert self.user.enforce_2FA is True ``` --- ### Inconvénients des fixtures comme données de test - pénible à maintenir - manque de lisibilité des tests - affecte négativement la performance - données figées = code moins robuste --- ### Idéalement il faudrait… - pouvoir générer des données à la volée, selon les besoins du test - ne spécifier que les données pertinentes pour le test - que ça soit facile ! --- ### Bonjour factory_boy https://factoryboy.readthedocs.io > As a fixtures replacement tool, **it aims to replace static, hard to maintain fixtures with easy-to-use factories** for complex object. `pip install factory_boy` --- ### Une factory simple ```python import factory class ProductFactory(factory.Factory): name = factory.Faker('catch_phrase') price = factory.Faker( 'pyfloat', min_value=1, max_value=100, right_digits=2, ) class Meta: model = dict ``` --- ### Utilisation ```python >>> ProductFactory() {'name': 'Cross-platform alliance', 'price': 34.52} >>> ProductFactory(name="Neutron parser") {'name': 'Neutron parser', 'price': 84.45} >>> ProductFactory(price=9.99) {'name': 'Assimilated groupware', 'price': 9.99} ``` --- ### Avec des modèles Django ```python # models.py from django.db import models class Product(models.Model): name = models.CharField(max_length=255) price = models.FloatField() ``` ```python import factory from . import models class ProductFactory(factory.DjangoModelFactory): name = factory.Faker('catch_phrase') price = factory.Faker( 'pyfloat', min_value=1, max_value=1000, right_digits=2, ) class Meta: model = models.Product ``` --- ### Objets liés 1/3 ```python # models.py class Product(models.Model): name = models.CharField(max_length=255) price = models.FloatField() class Order(models.Model): product = models.ForeignKey(Product) user = models.ForeignKey('auth.User') is_paid = models.BooleanField(default=False) is_shipped = models.BooleanField(default=False) ``` --- ### Objets liés 2/3 ```python class UserFactory(factory.DjangoModelFactory): username = factory.Faker('user_name') email = factory.Faker('email') class Meta: model = User class OrderFactory(factory.DjangoModelFactory): user = factory.SubFactory(UserFactory) product = factory.SubFactory(ProductFactory) class Meta: model = models.Order ``` --- ### Objets liés 3/3 ```python order = OrderFactory( user__email='eliot@me.com', product__price=12, ) assert order.user.email == 'eliot@me.com' assert card.product.price == 12 ``` --- ### Traits (1/2) Traits = raccourcis vers un état ```python class ProductFactory(factory.DjangoModelFactory): # … class Params: expensive = factory.Trait(price=999) class OrderFactory(factory.DjangoModelFactory): # … class Params: completed = factory.Trait( is_paid=True, is_shipped=True, ) ``` --- ### Traits (2/2) ```python expensive_order = OrderFactory(product__expensive=True) assert expensive_order.product.price == 999 completed_order = OrderFactory(completed=True) assert completed_order.is_paid is True assert completed_order.is_shipped is True ``` --- ### Build vs. create Persister en base de données ou non ```python order = OrderFactory.create() assert order.pk is not None order = OrderFactory.build() assert order.pk is None ``` --- ### Batch Créer des objets en lot ```python orders = OrderFactory.create_batch(42) assert len(orders) == 42 user = UserFactory() orders = OrderFactory.create_batch(5, user=user) for order in orders: assert order.user == user orders = OrderFactory.build_batch(5) for order in orders: assert order.pk is None ``` --- ### Aller plus loin : Faker https://faker.readthedocs.io/en/master/ ```python import random from faker.providers import lorem class MyAppProvider(lorem.Provider): def product_name(self): choices = ['Foo', 'Bar', 'Baz'] return random.choice(choices) factory.Faker.add_provider(MyAppProvider) class ProductFactory(factory.Factory): name = factory.Faker('product_name') ``` --- ### Aller plus loin : données de dev Peupler une base de données: ```bash python manage.py load_test_data \ --artists=1000 \ --albums=3000 \ --tracks=15000 Creating 1000 artists… Creating 3000 albums… Creating 15000 tracks… Final state of database: - 15774 tracks objects - 3179 albums objects - 1147 artists objects ``` <small> <a href="https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/common/management/commands/load_test_data.py">https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/common/management/commands/load_test_data.py</a> </small> --- ### Merci À vos questions ! - Email : contact@eliotberriot.com - Mastodon : [eliotberriot@mastodon.eliotberriot.com](https://mastodon.eliotberriot.com/@eliotberriot) - Slides: https://pad.funkwhale.audio/p/djangocong-factory-boy - Funkwhale: https://funkwhale.audio
{"tags":"presentation, django, cong","title":"Générer des données pour vos tests avec factory_boy","type":"slide","slideOptions":{"transition":"slide","transitionSpeed":"fast","theme":"sky","progress":true,"mouseWheel":true,"showNotes":false}}