factory_boy é um substituto de fixtures baseado no factory_bot do thinkingbot.
Como ferramenta de substituição de acessórios, visa substituir acessórios estáticos e de difícil manutenção por fábricas fáceis de usar para objetos complexos.
Em vez de construir uma configuração de teste exaustiva com todas as combinações possíveis de casos extremos, factory_boy
permite que você use objetos personalizados para o teste atual, declarando apenas os campos específicos do teste:
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 foi projetado para funcionar bem com vários ORMs (Django, MongoDB, SQLAlchemy) e pode ser facilmente estendido para outras bibliotecas.
Suas principais características incluem:
PyPI: https://pypi.org/project/factory-boy/
$ pip install factory_boy
Fonte: https://github.com/FactoryBoy/factory_boy/
$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install
Observação
Esta seção fornece um rápido resumo dos recursos do factory_boy. Uma listagem mais detalhada está disponível na documentação completa.
As fábricas declaram um conjunto de atributos usados para instanciar um objeto Python. A classe do objeto deve ser definida no campo model
de um atributo class Meta:
:
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
A integração de factory_boy com ferramentas de Mapeamento Relacional de Objetos (ORM) é fornecida por meio de factory.Factory
específicas.Subclasses de fábrica:
factory.django.DjangoModelFactory
factory.mogo.MogoFactory
factory.mongoengine.MongoEngineFactory
factory.alchemy.SQLAlchemyModelFactory
Mais detalhes podem ser encontrados na seção ORM.
factory_boy oferece suporte a diversas estratégias de instanciação diferentes: build, create e 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 ()
Você pode usar a classe Factory como atalho para a estratégia de instanciação padrão:
# Same as UserFactory.create()
user = UserFactory ()
Não importa qual estratégia seja usada, é possível substituir os atributos definidos passando argumentos de palavras-chave:
# Build a User instance and override first_name
>>> user = UserFactory.build( first_name = ' Joe ' )
>>> user.first_name
"Joe"
Também é possível criar vários objetos em uma única chamada:
>>> 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"]
As demonstrações ficam melhores com valores aleatórios, mas realistas; e esses valores realistas também podem ajudar a descobrir bugs. Para isso, factory_boy conta com a 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>
O uso de dados totalmente aleatórios em testes rapidamente se torna um problema para a reprodução de construções quebradas. Para isso, factory_boy fornece ajudantes para lidar com as sementes aleatórias que utiliza, localizadas no módulo factory.random
:
import factory . random
def setup_test_environment ():
factory . random . reseed_random ( 'my_awesome_project' )
# Other setup here
A maioria dos atributos de fábrica podem ser adicionados usando valores estáticos que são avaliados quando a fábrica é definida, mas alguns atributos (como campos cujo valor é calculado a partir de outros elementos) precisarão de valores atribuídos sempre que uma instância for gerada.
Esses atributos "preguiçosos" podem ser adicionados da seguinte forma:
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]"
Observação
LazyAttribute
chama a função com o objeto que está sendo construído como argumento, quando LazyFunction
não envia nenhum argumento.
Valores exclusivos em um formato específico (por exemplo, endereços de email) podem ser gerados usando sequências. As sequências são definidas usando Sequence
ou a sequence
do decorador:
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]'
Alguns objetos possuem um campo complexo, que deve ser definido a partir de fábricas dedicadas. Isso é tratado pelo auxiliar SubFactory
:
class PostFactory ( factory . Factory ):
class Meta :
model = models . Post
author = factory . SubFactory ( UserFactory )
A estratégia do objeto associado será utilizada:
# 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
suporta versões ativas do Python, bem como PyPy3.
A depuração de factory_boy pode ser bastante complexa devido às longas cadeias de chamadas. O registro detalhado está disponível através do registrador factory
.
Um auxiliar, factory.debug(), está disponível para facilitar a depuração:
with factory . debug ():
obj = TestModel2Factory ()
import logging
logger = logging . getLogger ( 'factory' )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . DEBUG )
Isso produzirá mensagens semelhantes a estas (recuo 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 é distribuído sob a licença MIT.
Os problemas devem ser abertos através do GitHub Issues; sempre que possível, uma solicitação pull deve ser incluída. Perguntas e sugestões são bem-vindas na lista de discussão.
As dependências de desenvolvimento podem ser instaladas em um virtualenv com:
$ pip install --editable ' .[dev] '
Todas as solicitações pull devem passar no conjunto de testes, que pode ser iniciado simplesmente com:
$ make testall
Para testar a cobertura, use:
$ make coverage
Para testar com uma versão específica do framework, você pode usar um alvo 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 usuários interessados em empacotar o FactoryBoy em canais de distribuição downstream (por exemplo, .deb
, .rpm
, .ebuild
), as dicas a seguir podem ser úteis:
As dependências de tempo de execução do pacote estão listadas em setup.cfg
. As dependências úteis para construir e testar a biblioteca são cobertas pelos extras dev
e doc
.
Além disso, todas as tarefas de desenvolvimento/teste são conduzidas por make(1)
.
Para executar as etapas de construção (atualmente apenas para documentos), execute:
python setup.py egg_info
make doc
Ao testar o ambiente Python ativo, execute o seguinte:
make test
Observação
Você deve ter certeza de que o módulo factory
é importável, pois é importado do código de teste.