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"
1 回の呼び出しで多数のオブジェクトを作成することもできます。
>>> 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
モジュールはテスト コードからインポートされるため、インポート可能であることを確認する必要があります。