[英]Storing factory-boy RelatedFactory Object on Parent Factory
[英]Passing an object created with SubFactory and LazyAttribute to a RelatedFactory in factory_boy
我使用factory.LazyAttribute
一個內SubFactory
調用中的對象,在創建的傳遞factory_parent
。 這很好用。
但是,如果我通過創建一個對象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
的相同調用在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)
我們可以使用相同的命名約定將對象傳遞給主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'}
我通過在@factory.post_generation
調用工廠解決了這個問題。 嚴格來說,這不是解決所提出的具體問題的方法,但我將在下面詳細解釋為什么最終成為一個更好的架構。 @ rhunwick的解決方案確實將SubFactory(LazyAttribute(''))
傳遞給了RelatedFactory
,但是仍然存在限制,這意味着這不適合我的情況。
我們將sub_object
和object
的創建從ProblemFactory
到ObjectWithSubObjectsFactory
(並刪除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_1
和column_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.