factory_boy是基于thoughtbot的factory_bot的固定装置替代品。
作为一个固定装置替换工具,它的目标是用易于使用的复杂对象工厂来取代静态的、难以维护的固定装置。
factory_boy
允许您使用为当前测试定制的对象,同时仅声明特定于测试的字段,而不是使用每种可能的极端情况组合来构建详尽的测试设置:
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 旨在与各种 ORM(Django、MongoDB、SQLAlchemy)良好配合,并且可以轻松扩展为其他库。
其主要特点包括:
PyPI:https://pypi.org/project/factory-boy/
$ pip install factory_boy
来源:https://github.com/FactoryBoy/factory_boy/
$ git clone git://github.com/FactoryBoy/factory_boy/
$ python setup.py install
笔记
本节提供了factory_boy 功能的快速摘要。完整文档中提供了更详细的列表。
工厂声明一组用于实例化 Python 对象的属性。对象的类必须在class Meta:
属性的model
字段中定义:
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
factory_boy 与对象关系映射(ORM)工具的集成是通过特定的factory.Factory
子类提供的:
factory.django.DjangoModelFactory
factory.mogo.MogoFactory
factory.mongoengine.MongoEngineFactory
factory.alchemy.SQLAlchemyModelFactory
更多详细信息可以在 ORM 部分找到。
Factory_boy 支持几种不同的实例化策略:构建、创建和存根:
# 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 ()
您可以使用 Factory 类作为默认实例化策略的快捷方式:
# Same as UserFactory.create()
user = UserFactory ()
无论使用哪种策略,都可以通过传递关键字参数来覆盖定义的属性:
# Build a User instance and override first_name
>>> user = UserFactory.build( first_name = ' Joe ' )
>>> user.first_name
"Joe"
也可以在一次调用中创建一堆对象:
>>> 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"]
具有随机但现实的值的演示看起来更好;这些现实的值也可以帮助发现错误。为此,factory_boy 依赖于优秀的 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>
在测试中使用完全随机的数据很快就会成为重现损坏的构建的问题。为此,factory_boy 提供了帮助程序来处理它使用的随机种子,位于factory.random
模块中:
import factory . random
def setup_test_environment ():
factory . random . reseed_random ( 'my_awesome_project' )
# Other setup here
大多数工厂属性可以使用在定义工厂时评估的静态值来添加,但某些属性(例如其值是根据其他元素计算的字段)将需要在每次生成实例时分配值。
这些“惰性”属性可以添加如下:
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]"
笔记
当LazyFunction
不发送任何参数时, LazyAttribute
会使用正在构造的对象作为参数来调用函数。
可以使用序列生成特定格式的唯一值(例如电子邮件地址)。序列是通过使用Sequence
或装饰器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]'
有些对象有一个复杂的字段,它本身应该从专用的工厂定义。这是由SubFactory
帮助程序处理的:
class PostFactory ( factory . Factory ):
class Meta :
model = models . Post
author = factory . SubFactory ( UserFactory )
将使用关联对象的策略:
# 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
支持活跃的 Python 版本以及 PyPy3。
由于调用链较长,调试factory_boy 可能相当复杂。可通过factory
记录器进行详细记录。
一个助手,factory.debug(),可以用来简化调试:
with factory . debug ():
obj = TestModel2Factory ()
import logging
logger = logging . getLogger ( 'factory' )
logger . addHandler ( logging . StreamHandler ())
logger . setLevel ( logging . DEBUG )
这将产生类似于这些的消息(人工缩进):
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 是根据 MIT 许可证分发的。
应通过 GitHub Issues 打开问题;只要有可能,就应该包含拉取请求。欢迎在邮件列表中提出问题和建议。
开发依赖项可以通过以下方式安装在 virtualenv 中:
$ pip install --editable ' .[dev] '
所有拉取请求都应通过测试套件,只需使用以下命令即可启动测试套件:
$ make testall
为了测试覆盖范围,请使用:
$ make coverage
要使用特定框架版本进行测试,您可以使用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
对于有兴趣将 FactoryBoy 打包到下游分发渠道(例如.deb
、 .rpm
、 .ebuild
)的用户,以下提示可能会有所帮助:
setup.cfg
中列出了包的运行时依赖项。 dev
和doc
附加内容涵盖了对构建和测试库有用的依赖项。
此外,所有开发/测试任务都是通过make(1)
驱动的。
为了运行构建步骤(当前仅适用于文档),请运行:
python setup.py egg_info
make doc
测试活动 Python 环境时,运行以下命令:
make test
笔记
您必须确保factory
模块是可导入的,因为它是从测试代码导入的。