Factory_boy ist ein Fixture-Ersatz, der auf dem Factory_Bot von Thoughtbot basiert.
Als Werkzeug zum Austausch von Vorrichtungen zielt es darauf ab, statische, schwer zu wartende Vorrichtungen durch benutzerfreundliche Fabriken für komplexe Objekte zu ersetzen.
Anstatt einen umfassenden Testaufbau mit allen möglichen Kombinationen von Eckfällen zu erstellen, können Sie factory_boy
für den aktuellen Test angepasste Objekte verwenden und dabei nur die testspezifischen Felder deklarieren:
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 ist so konzipiert, dass es gut mit verschiedenen ORMs (Django, MongoDB, SQLAlchemy) funktioniert und kann problemlos für andere Bibliotheken erweitert werden.
Zu seinen Hauptmerkmalen gehören:
PyPI: https://pypi.org/project/factory-boy/
$ pip install factory_boy
Quelle: https://github.com/FactoryBoy/factory_boy/
$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install
Notiz
Dieser Abschnitt bietet eine kurze Zusammenfassung der Funktionen von Factory_boy. Eine detailliertere Auflistung finden Sie in der vollständigen Dokumentation.
Fabriken deklarieren eine Reihe von Attributen, die zum Instanziieren eines Python-Objekts verwendet werden. Die Klasse des Objekts muss im model
eines class Meta:
-Attributs definiert werden:
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
Die Factory_boy-Integration mit Object Relational Mapping (ORM)-Tools wird über bestimmte factory.Factory
Unterklassen bereitgestellt:
factory.django.DjangoModelFactory
factory.mogo.MogoFactory
factory.mongoengine.MongoEngineFactory
factory.alchemy.SQLAlchemyModelFactory
Weitere Details finden Sie im ORM-Bereich.
Factory_boy unterstützt mehrere verschiedene Instanziierungsstrategien: Build, Create und 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 ()
Sie können die Factory-Klasse als Abkürzung für die Standard-Instanziierungsstrategie verwenden:
# Same as UserFactory.create()
user = UserFactory ()
Unabhängig von der verwendeten Strategie ist es möglich, die definierten Attribute durch die Übergabe von Schlüsselwortargumenten zu überschreiben:
# Build a User instance and override first_name
>>> user = UserFactory.build( first_name = ' Joe ' )
>>> user.first_name
"Joe"
Es ist auch möglich, mehrere Objekte in einem einzigen Aufruf zu erstellen:
>>> 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"]
Demos sehen mit zufälligen, aber realistischen Werten besser aus; Und diese realistischen Werte können auch dabei helfen, Fehler zu entdecken. Factory_boy greift dabei auf die hervorragende Faker-Bibliothek zurück:
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>
Die Verwendung vollständig randomisierter Daten in Tests stellt bei der Reproduktion fehlerhafter Builds schnell ein Problem dar. Zu diesem Zweck stellt „factory_boy“ im Modul factory.random
Hilfsprogramme für die Verarbeitung der verwendeten Zufalls-Seeds zur Verfügung:
import factory . random
def setup_test_environment ():
factory . random . reseed_random ( 'my_awesome_project' )
# Other setup here
Die meisten Factory-Attribute können mit statischen Werten hinzugefügt werden, die beim Definieren der Factory ausgewertet werden, aber einige Attribute (z. B. Felder, deren Wert aus anderen Elementen berechnet wird) müssen bei jeder Instanzgenerierung Werte zugewiesen werden.
Diese „faulen“ Attribute können wie folgt hinzugefügt werden:
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]"
Notiz
LazyAttribute
ruft die Funktion mit dem als Argument erstellten Objekt auf, wenn LazyFunction
kein Argument sendet.
Mithilfe von Sequenzen können eindeutige Werte in einem bestimmten Format (z. B. E-Mail-Adressen) generiert werden. Sequenzen werden mithilfe von Sequence
oder der sequence
definiert:
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]'
Einige Objekte verfügen über ein komplexes Feld, das selbst in einer speziellen Fabrik definiert werden sollte. Dies wird vom SubFactory
-Helfer erledigt:
class PostFactory ( factory . Factory ):
class Meta :
model = models . Post
author = factory . SubFactory ( UserFactory )
Die Strategie des zugehörigen Objekts wird verwendet:
# 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
unterstützt aktive Python-Versionen sowie PyPy3.
Das Debuggen von „factory_boy“ kann aufgrund der langen Aufrufketten recht komplex sein. Eine detaillierte Protokollierung ist über den factory
verfügbar.
Zur Vereinfachung des Debuggens steht ein Hilfsprogramm namens „factory.debug()“ zur Verfügung:
with factory . debug ():
obj = TestModel2Factory ()
import logging
logger = logging . getLogger ( 'factory' )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . DEBUG )
Dies führt zu ähnlichen Meldungen (künstliche Einrückung):
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 wird unter der MIT-Lizenz vertrieben.
Probleme sollten über GitHub Issues geöffnet werden; Wann immer möglich, sollte eine Pull-Anfrage enthalten sein. Fragen und Anregungen sind auf der Mailingliste willkommen.
Entwicklungsabhängigkeiten können in einer virtuellen Umgebung installiert werden mit:
$ pip install --editable ' .[dev] '
Alle Pull-Anfragen sollten die Testsuite bestehen, die einfach gestartet werden kann mit:
$ make testall
Um die Abdeckung zu testen, verwenden Sie bitte:
$ make coverage
Um mit einer bestimmten Framework-Version zu testen, können Sie ein tox
-Ziel verwenden:
# 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
Für Benutzer, die daran interessiert sind, FactoryBoy in nachgelagerte Vertriebskanäle (z. B. .deb
, .rpm
, .ebuild
) zu packen, könnten die folgenden Tipps hilfreich sein:
Die Laufzeitabhängigkeiten des Pakets sind in setup.cfg
aufgeführt. Die zum Erstellen und Testen der Bibliothek nützlichen Abhängigkeiten werden durch die dev
und doc
Extras abgedeckt.
Darüber hinaus werden alle Entwicklungs-/Testaufgaben über make(1)
gesteuert.
Um die Build-Schritte auszuführen (derzeit nur für Dokumente), führen Sie Folgendes aus:
python setup.py egg_info
make doc
Führen Sie beim Testen für die aktive Python-Umgebung Folgendes aus:
make test
Notiz
Sie müssen sicherstellen, dass das factory
Modul importierbar ist, da es aus dem Testcode importiert wird.