factory_boy es un reemplazo de accesorios basado en factory_bot de thinkbot.
Como herramienta de reemplazo de accesorios, su objetivo es reemplazar accesorios estáticos y difíciles de mantener con fábricas fáciles de usar para objetos complejos.
En lugar de crear una configuración de prueba exhaustiva con todas las combinaciones posibles de casos de esquina, factory_boy
le permite usar objetos personalizados para la prueba actual, mientras solo declara los campos específicos de la prueba:
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á diseñado para funcionar bien con varios ORM (Django, MongoDB, SQLAlchemy) y puede ampliarse fácilmente a otras bibliotecas.
Sus principales características incluyen:
PyPI: https://pypi.org/project/factory-boy/
$ pip install factory_boy
Fuente: https://github.com/FactoryBoy/factory_boy/
$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install
Nota
Esta sección proporciona un resumen rápido de las características de factory_boy. Una lista más detallada está disponible en la documentación completa.
Las fábricas declaran un conjunto de atributos utilizados para crear instancias de un objeto Python. La clase del objeto debe estar definida en el campo model
de una class Meta:
atributo:
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
La integración de factory_boy con las herramientas de mapeo relacional de objetos (ORM) se proporciona a través de subclases específicas factory.Factory
:
factory.django.DjangoModelFactory
factory.mogo.MogoFactory
factory.mongoengine.MongoEngineFactory
factory.alchemy.SQLAlchemyModelFactory
Se pueden encontrar más detalles en la sección ORM.
factory_boy admite varias estrategias de creación de instancias diferentes: compilación, creación y código auxiliar:
# 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 ()
Puede utilizar la clase Factory como atajo para la estrategia de creación de instancias predeterminada:
# Same as UserFactory.create()
user = UserFactory ()
No importa qué estrategia se utilice, es posible anular los atributos definidos pasando argumentos de palabras clave:
# Build a User instance and override first_name
>>> user = UserFactory.build( first_name = ' Joe ' )
>>> user.first_name
"Joe"
También es posible crear varios objetos en una sola llamada:
>>> 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"]
Las demostraciones se ven mejor con valores aleatorios pero realistas; y esos valores realistas también pueden ayudar a descubrir errores. Para ello, factory_boy confía en la excelente biblioteca 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>
El uso de datos totalmente aleatorios en las pruebas se convierte rápidamente en un problema para reproducir compilaciones defectuosas. Para ello, factory_boy proporciona ayudas para manejar las semillas aleatorias que utiliza, ubicadas en el módulo factory.random
:
import factory . random
def setup_test_environment ():
factory . random . reseed_random ( 'my_awesome_project' )
# Other setup here
La mayoría de los atributos de fábrica se pueden agregar utilizando valores estáticos que se evalúan cuando se define la fábrica, pero algunos atributos (como los campos cuyo valor se calcula a partir de otros elementos) necesitarán valores asignados cada vez que se genera una instancia.
Estos atributos "perezosos" se pueden agregar de la siguiente manera:
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]"
Nota
LazyAttribute
llama a la función con el objeto que se está construyendo como argumento, cuando LazyFunction
no envía ningún argumento.
Se pueden generar valores únicos en un formato específico (por ejemplo, direcciones de correo electrónico) mediante secuencias. Las secuencias se definen utilizando Sequence
o el decorador sequence
:
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]'
Algunos objetos tienen un campo complejo, que a su vez debe definirse desde fábricas dedicadas. Esto lo maneja el asistente SubFactory
:
class PostFactory ( factory . Factory ):
class Meta :
model = models . Post
author = factory . SubFactory ( UserFactory )
Se utilizará la estrategia del objeto asociado:
# 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
admite versiones activas de Python y PyPy3.
La depuración de factory_boy puede resultar bastante compleja debido a las largas cadenas de llamadas. El registro detallado está disponible a través del registrador factory
.
Hay disponible un asistente, factory.debug(), para facilitar la depuración:
with factory . debug ():
obj = TestModel2Factory ()
import logging
logger = logging . getLogger ( 'factory' )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . DEBUG )
Esto generará mensajes similares a estos (sangría artificial):
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 se distribuye bajo la licencia MIT.
Los Issues deben abrirse a través de GitHub Issues; Siempre que sea posible, se debe incluir una solicitud de extracción. Preguntas y sugerencias son bienvenidas en la lista de correo.
Las dependencias de desarrollo se pueden instalar en un entorno virtual con:
$ pip install --editable ' .[dev] '
Todas las solicitudes de extracción deben pasar el conjunto de pruebas, que se puede iniciar simplemente con:
$ make testall
Para probar la cobertura, utilice:
$ make coverage
Para realizar pruebas con una versión específica del marco, puede utilizar un objetivo 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
Para los usuarios interesados en empaquetar FactoryBoy en canales de distribución posteriores (por ejemplo, .deb
, .rpm
, .ebuild
), los siguientes consejos pueden resultar útiles:
Las dependencias de tiempo de ejecución del paquete se enumeran en setup.cfg
. Las dependencias útiles para crear y probar la biblioteca están cubiertas por los extras dev
y doc
.
Además, todas las tareas de desarrollo/pruebas se realizan a través de make(1)
.
Para ejecutar los pasos de compilación (actualmente solo para documentos), ejecute:
python setup.py egg_info
make doc
Al probar el entorno Python activo, ejecute lo siguiente:
make test
Nota
Debe asegurarse de que el módulo factory
sea importable, ya que se importa desde el código de prueba.