2015-10-12 25 views
6

SubFactoryコール内でfactory.LazyAttributeを使用して、factory_parentで作成されたオブジェクトを渡します。これは正常に動作します。SubFactoryとLazyAttributeで作成したオブジェクトをfactory_boyのRelatedFactoryに渡す

ただし、作成したオブジェクトをRelatedFactoryに渡すと、LazyAttributeにはfactory_parentが表示されなくなり、失敗します。

これは正常に動作します:

class OKFactory(factory.DjangoModelFactory): 
    class = Meta: 
     model = Foo 
     exclude = ['sub_object'] 

    sub_object = factory.SubFactory(SubObjectFactory) 

    object = factory.SubFactory(ObjectFactory, 
     sub_object=factory.LazyAttribute(lambda obj: obj.factory_parent.sub_object)) 

LazyAttributeと同じ呼び出しがここに失敗:

class ProblemFactory(OKFactory): 
    class = Meta: 
     model = Foo 
     exclude = ['sub_object', 'object'] 

    sub_object = factory.SubFactory(SubObjectFactory) 

    object = factory.SubFactory(ObjectFactory, 
     sub_object=factory.LazyAttribute(lambda obj: obj.factory_parent.sub_object)) 

    another_object = factory.RelatedFactory(AnotherObjectFactory, 'foo', object=object) 

同じLazyAttribute呼び出しはもはやfactory_parentを見ることができない、とだけAnotherObject値にアクセスすることができます。 LazyAttributeは次のエラーをスローします:

AttributeError: The parameter sub_object is unknown. Evaluated attributes are...[then lists all attributes of AnotherObjectFactory] 

これには方法がありますか?

私はただのObjectFactory呼び出し、すなわちにsub_object = sub_objectを置くことはできません。

sub_object = factory.SubFactory(SubObjectFactory) 
    object = factory.SubFactory(ObjectFactory, sub_object=sub_object) 

私はその後んので場合:

object2 = factory.SubFactory(ObjectFactory, sub_object=sub_object) 

二sub_objectが作成され、私は両方必要なのに対し、同じsub_objectを参照するオブジェクト。私は役に立たないとSelfAttributeを試してみました。

+0

私は同じpkを指定すると問題を解決できると思います。 –

+0

'sub_object_id = sub_object_id'を意味していますか?または 'LazyAttribute(lambda obj:obj.factory_parent.sub_object.id)'? – Chris

+0

[この質問](http://stackoverflow.com/questions/32995158/getting-id-of-associated-child-records-in-factory-boy/33043428#33043428)への回答は、別の方法を示すようですが、しかし、私はそれをこの要件に適合させることはできません。 (この質問は、これらの回答の1つにバグを修正することです)。 – Chris

答えて

4

あなたは、あなたが望むものを達成するためにRelatedFactoryに渡されたパラメータをオーバーライドする能力を活用できると思います。例えば

、与えられた:

class MyFactory(OKFactory): 

    object = factory.SubFactory(MyOtherFactory) 
    related = factory.RelatedFactory(YetAnotherFactory) # We want to pass object in here 

を我々はobjectの値が事前にあると何が起こっていたか知っていたならば、我々はそれが何かを動作させることができます:

我々が使用できる
object = MyOtherFactory() 
thing = MyFactory(object=object, related__param=object) 

この同じ命名規則は、オブジェクトをFactoryの中のRelatedFactoryに渡すためのものです。

class MyFactory(OKFactory): 

    class Meta: 
     exclude = ['object'] 

    object = factory.SubFactory(MyOtherFactory) 
    related__param = factory.SelfAttribute('object') 
    related__otherrelated__param = factory.LazyAttribute(lambda myobject: 'admin%d_%d' % (myobject.level, myobject.level - 1)) 
    related = factory.RelatedFactory(YetAnotherFactory) # Will be called with {'param': object, 'otherrelated__param: 'admin1_2'} 
2

@factory.post_generationの工場を呼び出すだけで解決しました。厳密に言えば、これは具体的な問題の解決策ではありませんが、なぜこれがより良いアーキテクチャーになったのかを以下に詳しく説明します。 @ rhunwickのソリューションはSubFactory(LazyAttribute(''))を真にRelatedFactoryに渡しますが、これは私の状況では正しくないという制限が残っていました。

我々はObjectWithSubObjectsFactoryProblemFactoryからsub_objectobjectの作成を動かす(およびexclude句を削除)、およびProblemFactoryの末尾に次のコードを追加します。

@factory.post_generation 
def post(self, create, extracted, **kwargs): 
    if not create: 
     return # No IDs, so wouldn't work anyway 

    object = ObjectWithSubObjectsFactory() 
    sub_object_ids_by_code = dict((sbj.name, sbj.id) for sbj in object.subobject_set.all()) 

    # self is the `Foo` Django object just created by the `ProblemFactory` that contains this code. 
    for another_obj in self.anotherobject_set.all(): 
     if another_obj.name == 'age_in': 
      another_obj.attribute_id = sub_object_ids_by_code['Age'] 
      another_obj.save() 
     elif another_obj.name == 'income_in': 
      another_obj.attribute_id = sub_object_ids_by_code['Income'] 
      another_obj.save() 

だから、RelatedFactoryコールがPostGeneration呼び出し前に実行されているようです。 this question

命名は理解しやすいですので、ここではそのサンプル問題の同じ溶液のコードは次のとおりです。

datasetcolumn_1column_2の創出新工場DatasetAnd2ColumnsFactoryに移動し、以下のコードがあるさFunctionToParameterSettingsFactoryの末尾に追加されます。

@factory.post_generation 
def post(self, create, extracted, **kwargs): 
    if not create: 
     return 

    dataset = DatasetAnd2ColumnsFactory() 
    column_ids_by_name = 
     dict((column.name, column.id) for column in dataset.column_set.all()) 

    # self is the `FunctionInstantiation` Django object just created by the `FunctionToParameterSettingsFactory` that contains this code. 
    for parameter_setting in self.parametersetting_set.all(): 
     if parameter_setting.name == 'age_in': 
      parameter_setting.column_id = column_ids_by_name['Age'] 
      parameter_setting.save() 
     elif parameter_setting.name == 'income_in': 
      parameter_setting.column_id = column_ids_by_name['Income'] 
      parameter_setting.save() 

私は、このように、工場を設定するオプションを渡し、このアプローチを拡張:

whatever = WhateverFactory(options__an_option=True, options__another_option=True) 

次に、このファクトリコードオプションを検出し、必要なテストデータを生成した(方法をに変更され注意パラメータ名のプレフィックスと一致するように、options):

@factory.post_generation 
def options(self, create, not_used, **kwargs): 

    # The standard code as above 

    if kwargs.get('an_option', None): 
     # code for custom option 'an_option' 
    if kwargs.get('another_option', None): 
     # code for custom option 'another_option' 

私はその後、さらにこれを拡張しました。私の望むモデルは自己結合を含んでいたので、私の工場は再帰的です。だから、のようなコールのために:私が持っている@factory.post_generation以内

whatever = WhateverFactory(options__an_option='xyz', 
          options__an_option_for_a_nested_whatever='abc') 

:あなたは、私は、このオプションではなく、私にrhunwicksの適切な解決策@行った理由を読む必要はありません

class Meta: 
    model = Whatever 
# self is the top level object being generated 

@factory.post_generation 
def options(self, create, not_used, **kwargs): 

    # This generates the nested object 
    nested_object = WhateverFactory(
     options__an_option=kwargs.get('an_option_for_a_nested_whatever', None)) 

    # then join nested_object to self via the self join 
    self.nested_whatever_id = nested_object.id 

いくつかの注意上記の質問。 2つの理由があった。

私がそれを実験するのを止めたのは、RelatedFactoryと世代後の順番が信頼できないということでした。明らかに無関係な要素がそれに影響します。おそらく遅延評価の結果です。明白な理由がなく工場が突然稼働を停止するというエラーがありました。一度、私はRelatedFactoryに割り当てられた変数の名前を変更したからです。これはばかげて聞こえるが、私はそれを死にかけてテストした(そしてhereを掲示した)が、変数の名前を変更することにより、RelatedFactoryとpost genの実行順序が確実に変更されたことは間違いない。私はまだそれが何か他の理由(私は診断することが決してなかった)のために再び起こるまで、これが私のためにいくつかの見落としであったと考えました。

第2に、宣言型コードが混乱し、柔軟性がなく、再因子付けするのが難しいことがわかりました。インスタンス化中に異なる設定を渡すのは簡単ではないので、テストデータのさまざまなバリエーションに対して同じファクトリを使用できるので、コードを繰り返す必要があります。objectは工場に追加する必要がありますMeta.excludeリスト - データを生成するコードの信頼性の高いエラーでした。開発者は、コントロールフローを理解するために、いくつかの工場を何回か渡す必要があります。これらのトリックを使い果たすまで、宣言的なボディーの間にジェネレーションコードが広がり、残りのコードはポストジェネレーションに入り込むか、非常に複雑になります。共通の例は、相互依存オブジェクトの別の三つ組(例えば、モデル、パラメータ値など)の外部キーとして、相互依存モデル(例えば、親子カテゴリ構造またはデータセット/属性/エンティティ)の三つ組である他のモデルのパラメータ値へ)。これらのタイプの構造のうちのいくつかは、特に入れ子になっていると、すぐに処理できなくなります。

私はそれが実際にfactory_boyの精神ではないことを認識していますが、すべてをこれらの問題を解決したポストジェネレーションに入れています。パラメータを渡すことができるので、同じ単一のファクトリがすべての複合モデルテストデータ要件を満たし、コードは繰り返されません。混乱する継承と上書きの連鎖に依存せず、いくつかのバグの影響を受けるのではなく、すぐにわかりやすく完全に信頼できる作成シーケンスを簡単に確認できます。インタラクションは明白であるため、機能を追加するためにすべてを消化する必要はなく、機能のさまざまな領域は、世代後のif節でグループ化されます。作業変数を除外する必要はなく、工場コードの期間中は参照することができます。ファンクションの記述はファクトリクラス名ではなくパラメータ名になるため、単体テストコードは簡略化されているため、WhateverCreateXYZCreateABC..()ではなくWhateverFactory(options__create_xyz=True, options__create_abc=True..のような呼び出しでデータを作成します。これは、コードのきれいな責任の分裂をきれいにします。

関連する問題