簡體   English   中英

將使用SubFactory和LazyAttribute創建的對象傳遞給factory_boy中的RelatedFactory

[英]Passing an object created with SubFactory and LazyAttribute to a RelatedFactory in factory_boy

我使用factory.LazyAttribute一個內SubFactory調用中的對象,在創建的傳遞factory_parent 這很好用。

但是,如果我通過創建一個對象RelatedFactoryLazyAttribute不再能看到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的相同調用在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]

這有什么方法嗎?

我不能只將sub_object = sub_object放入ObjectFactory調用,即:

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

因為如果我這樣做:

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

創建了第二個子對象,而我需要兩個對象來引用相同的子對象。 我試過SelfAttribute無濟於事。

我認為你可以利用覆蓋傳遞給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)

我們可以使用相同的命名約定將對象傳遞給主FactoryRelatedFactory

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'}

我通過在@factory.post_generation調用工廠解決了這個問題。 嚴格來說,這不是解決所提出的具體問題的方法,但我將在下面詳細解釋為什么最終成為一個更好的架構。 @ rhunwick的解決方案確實將SubFactory(LazyAttribute(''))傳遞給了RelatedFactory ,但是仍然存在限制,這意味着這不適合我的情況。

我們將sub_objectobject的創建從ProblemFactoryObjectWithSubObjectsFactory (並刪除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調用。

此問題中的命名更容易理解,因此以下是該示例問題的相同解決方案代碼:

dataset column_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'

然后我進一步擴展了這個。 因為我想要的模型包含自連接,所以我的工廠是遞歸的。 所以對於如下呼叫:

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

@factory.post_generation我有:

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

一些注意事項你不需要閱讀為什么我選擇這個選項而不是@ rhunwicks正確解決我上面的問題。 有兩個原因。

阻止我試驗它的事情是,RelatedFactory和后代的順序不可靠 - 顯然不相關的因素影響它,可能是懶惰評估的結果。 我有錯誤,一組工廠突然停止工作沒有明顯的原因。 曾經是因為我重命名了變量RelatedFactory被分配給。 這聽起來很荒謬,但我測試了它(並在發布)但毫無疑問 - 重命名變量可靠地切換了RelatedFactory和post-gen執行的序列。 我仍然認為這是對我的一些疏忽,直到它因某些其他原因(我從未設法診斷)再次發生。

其次,我發現聲明性代碼令人困惑,缺乏靈活性,難以重新考慮因素。 在實例化過程中傳遞不同的配置是不容易的,這樣同一個工廠就可以用於測試數據的不同變化,這意味着我不得不重復代碼, object需要添加到Factory Meta.exclude列表 - 聽起來微不足道但是當你'產生數據的代碼頁面是一個可靠的錯誤。 作為開發人員,您必須多次通過多個工廠才能了解控制流程。 生成代碼將在聲明性主體之間傳播,直到你用完這些技巧,然后其余的將進入后代或變得非常復雜。 對我來說一個常見的例子是三個相互依賴的模型(例如,父子類別結構或數據集/屬性/實體)作為另一個相互依賴的對象三元組的外鍵(例如,模型,參數值等,引用到其他模型的參數值)。 這些類型的結構中的一些,特別是如果嵌套,很快就變得難以管理。

我意識到這並不是真正符合factory_boy的精神,而是把所有東西都放到了后代,解決了所有這些問題。 我可以傳入參數,因此同一個工廠可以滿足我所有的復合模型測試數據要求,並且不會重復任何代碼。 創建的順序很容易立即,明顯和完全可靠,而不是依賴於混亂的繼承和覆蓋鏈,並受到一些錯誤。 交互是顯而易見的,因此您不需要消化整個事物來添加一些功能,並且不同的功能區域被分組在后代if子句中。 無需排除工作變量,您可以在工廠代碼的持續時間內引用它們。 單元測試代碼是簡化的,因為描述功能在參數名稱而不是Factory類名稱中 - 因此您使用WhateverFactory(options__create_xyz=True, options__create_abc=True.. ,而不是WhateverCreateXYZCreateABC..()調用創建數據。對代碼進行了很好的責任划分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM