factory_boy est un remplacement d'appareils basé sur factory_bot de Thoughtbot.
En tant qu'outil de remplacement de luminaires, il vise à remplacer les luminaires statiques et difficiles à entretenir par des usines faciles à utiliser pour les objets complexes.
Au lieu de construire une configuration de test exhaustive avec toutes les combinaisons possibles de cas extrêmes, factory_boy
vous permet d'utiliser des objets personnalisés pour le test en cours, tout en déclarant uniquement les champs spécifiques au test :
class FooTests ( unittest . TestCase ):
def test_with_factory_boy ( self ):
# We need a 200€, paid order, shipping to australia, for a VIP customer
order = OrderFactory (
amount = 200 ,
status = 'PAID' ,
customer__is_vip = True ,
address__country = 'AU' ,
)
# Run the tests here
def test_without_factory_boy ( self ):
address = Address (
street = "42 fubar street" ,
zipcode = "42Z42" ,
city = "Sydney" ,
country = "AU" ,
)
customer = Customer (
first_name = "John" ,
last_name = "Doe" ,
phone = "+1234" ,
email = "[email protected]" ,
active = True ,
is_vip = True ,
address = address ,
)
# etc.
factory_boy est conçu pour fonctionner correctement avec divers ORM (Django, MongoDB, SQLAlchemy) et peut facilement être étendu à d'autres bibliothèques.
Ses principales caractéristiques comprennent :
PyPI : https://pypi.org/project/factory-boy/
$ pip install factory_boy
Source : https://github.com/FactoryBoy/factory_boy/
$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install
Note
Cette section fournit un résumé rapide des fonctionnalités de factory_boy. Une liste plus détaillée est disponible dans la documentation complète.
Les usines déclarent un ensemble d'attributs utilisés pour instancier un objet Python. La classe de l'objet doit être définie dans le champ model
d'une class Meta:
attribut :
import factory
from . import models
class UserFactory ( factory . Factory ):
class Meta :
model = models . User
first_name = 'John'
last_name = 'Doe'
admin = False
# Another, different, factory for the same object
class AdminFactory ( factory . Factory ):
class Meta :
model = models . User
first_name = 'Admin'
last_name = 'User'
admin = True
L'intégration de factory_boy avec les outils de mappage objet-relationnel (ORM) est fournie via des sous-classes spécifiques factory.Factory
:
factory.django.DjangoModelFactory
factory.mogo.MogoFactory
factory.mongoengine.MongoEngineFactory
factory.alchemy.SQLAlchemyModelFactory
Plus de détails peuvent être trouvés dans la section ORM.
factory_boy prend en charge plusieurs stratégies d'instanciation différentes : build, create et stub :
# Returns a User instance that's not saved
user = UserFactory . build ()
# Returns a saved User instance.
# UserFactory must subclass an ORM base class, such as DjangoModelFactory.
user = UserFactory . create ()
# Returns a stub object (just a bunch of attributes)
obj = UserFactory . stub ()
Vous pouvez utiliser la classe Factory comme raccourci pour la stratégie d'instanciation par défaut :
# Same as UserFactory.create()
user = UserFactory ()
Quelle que soit la stratégie utilisée, il est possible de remplacer les attributs définis en passant des arguments de mots clés :
# Build a User instance and override first_name
>>> user = UserFactory.build( first_name = ' Joe ' )
>>> user.first_name
"Joe"
Il est également possible de créer plusieurs objets en un seul appel :
>>> users = UserFactory.build_batch( 10 , first_name = " Joe " )
>>> len (users)
10
>>> [user.first_name for user in users]
["Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe"]
Les démos sont plus belles avec des valeurs aléatoires mais réalistes ; et ces valeurs réalistes peuvent également aider à découvrir des bugs. Pour cela, factory_boy s'appuie sur l'excellente bibliothèque faker :
class RandomUserFactory ( factory . Factory ):
class Meta :
model = models . User
first_name = factory . Faker ( 'first_name' )
last_name = factory . Faker ( 'last_name' )
>>> RandomUserFactory()
<User: Lucy Murray>
L’utilisation de données entièrement aléatoires dans les tests constitue rapidement un problème pour reproduire des builds défectueux. À cette fin, factory_boy fournit des aides pour gérer les graines aléatoires qu'il utilise, situées dans le module factory.random
:
import factory . random
def setup_test_environment ():
factory . random . reseed_random ( 'my_awesome_project' )
# Other setup here
La plupart des attributs de fabrique peuvent être ajoutés à l'aide de valeurs statiques qui sont évaluées lorsque la fabrique est définie, mais certains attributs (tels que les champs dont la valeur est calculée à partir d'autres éléments) nécessiteront l'attribution de valeurs à chaque fois qu'une instance est générée.
Ces attributs « paresseux » peuvent être ajoutés comme suit :
class UserFactory ( factory . Factory ):
class Meta :
model = models . User
first_name = 'Joe'
last_name = 'Blow'
email = factory . LazyAttribute ( lambda a : '{}.{}@example.com' . format ( a . first_name , a . last_name ). lower ())
date_joined = factory . LazyFunction ( datetime . now )
>>> UserFactory().email
"[email protected]"
Note
LazyAttribute
appelle la fonction avec l'objet en cours de construction comme argument, lorsque LazyFunction
n'envoie aucun argument.
Des valeurs uniques dans un format spécifique (par exemple, des adresses e-mail) peuvent être générées à l'aide de séquences. Les séquences sont définies en utilisant Sequence
ou la sequence
décorateur :
class UserFactory ( factory . Factory ):
class Meta :
model = models . User
email = factory . Sequence ( lambda n : 'person{}@example.com' . format ( n ))
> >> UserFactory (). email
'[email protected]'
> >> UserFactory (). email
'[email protected]'
Certains objets ont un domaine complexe, qui doit lui-même être défini à partir d'une usine dédiée. Ceci est géré par l'assistant SubFactory
:
class PostFactory ( factory . Factory ):
class Meta :
model = models . Post
author = factory . SubFactory ( UserFactory )
La stratégie de l'objet associé sera utilisée :
# Builds and saves a User and a Post
> >> post = PostFactory ()
> >> post . id is None # Post has been 'saved'
False
> >> post . author . id is None # post.author has been saved
False
# Builds but does not save a User, and then builds but does not save a Post
> >> post = PostFactory . build ()
> >> post . id is None
True
> >> post . author . id is None
True
factory_boy
prend en charge les versions Python actives ainsi que PyPy3.
Le débogage de factory_boy peut être assez complexe en raison des longues chaînes d'appels. Une journalisation détaillée est disponible via l’enregistreur factory
.
Un assistant, factory.debug(), est disponible pour faciliter le débogage :
with factory . debug ():
obj = TestModel2Factory ()
import logging
logger = logging . getLogger ( 'factory' )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . DEBUG )
Cela produira des messages similaires à ceux-ci (indentation artificielle) :
BaseFactory: Preparing tests.test_using.TestModel2Factory( extra ={})
LazyStub: Computing values for tests.test_using.TestModel2Factory( two =<OrderedDeclarationWrapper for <factory.declarations.SubFactory object at 0x1e15610>>)
SubFactory: Instantiating tests.test_using.TestModelFactory( __containers =(<LazyStub for tests.test_using.TestModel2Factory>,), one =4), create =True
BaseFactory: Preparing tests.test_using.TestModelFactory( extra ={ ' __containers ' : (<LazyStub for tests.test_using.TestModel2Factory>,), ' one ' : 4})
LazyStub: Computing values for tests.test_using.TestModelFactory( one =4)
LazyStub: Computed values, got tests.test_using.TestModelFactory( one =4)
BaseFactory: Generating tests.test_using.TestModelFactory( one =4)
LazyStub: Computed values, got tests.test_using.TestModel2Factory( two =<tests.test_using.TestModel object at 0x1e15410>)
BaseFactory: Generating tests.test_using.TestModel2Factory( two =<tests.test_using.TestModel object at 0x1e15410>)
factory_boy est distribué sous la licence MIT.
Les problèmes doivent être ouverts via GitHub Issues ; dans la mesure du possible, une pull request doit être incluse. Les questions et suggestions sont les bienvenues sur la liste de diffusion.
Les dépendances de développement peuvent être installées dans un virtualenv avec :
$ pip install --editable ' .[dev] '
Toutes les demandes d'extraction doivent réussir la suite de tests, qui peut être lancée simplement avec :
$ make testall
Afin de tester la couverture, veuillez utiliser :
$ make coverage
Pour tester avec une version spécifique du framework, vous pouvez utiliser une cible tox
:
# list all tox environments
$ tox --listenvs
# run tests inside a specific environment (django/mongoengine/SQLAlchemy are not installed)
$ tox -e py310
# run tests inside a specific environment (django)
$ tox -e py310-djangomain
# run tests inside a specific environment (alchemy)
$ tox -e py310-alchemy
# run tests inside a specific environment (mongoengine)
$ tox -e py310-mongo
Pour les utilisateurs souhaitant intégrer FactoryBoy dans des canaux de distribution en aval (par exemple .deb
, .rpm
, .ebuild
), les conseils suivants peuvent être utiles :
Les dépendances d'exécution du package sont répertoriées dans setup.cfg
. Les dépendances utiles pour construire et tester la bibliothèque sont couvertes par les extras dev
et doc
.
De plus, toutes les tâches de développement/test sont pilotées via make(1)
.
Afin d'exécuter les étapes de construction (actuellement uniquement pour les documents), exécutez :
python setup.py egg_info
make doc
Lors du test de l'environnement Python actif, exécutez ce qui suit :
make test
Note
Vous devez vous assurer que le module factory
est importable, car il est importé à partir du code de test.