我在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
的同一号召是失败:
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
无济于事。
答案 0 :(得分:8)
我认为您可以利用覆盖传递到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)
我们可以使用相同的命名约定将对象传递到主RelatedFactory
中的Factory
:
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'}
答案 1 :(得分: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
调用之前执行。
this question中的命名更容易理解,因此这个示例问题的解决方案代码相同:
dataset
,column_1
和column_2
的创建将移至新工厂DatasetAnd2ColumnsFactory
,然后将以下代码添加到{{1}的末尾}。
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
有些说明你不需要阅读为什么我选择这个选项而不是@ rhunwicks正确解决我上面的问题。有两个原因。
阻止我试验它的事情是,RelatedFactory和后代的顺序不可靠 - 显然不相关的因素影响它,可能是懒惰评估的结果。我有错误,一组工厂突然停止工作没有明显的原因。曾经是因为我重命名了变量RelatedFactory被分配给。这听起来很荒谬,但我测试了它死了(并发布了here)但毫无疑问 - 重命名变量可靠地切换了RelatedFactory和post-gen执行的序列。我仍然认为这是对我的一些疏忽,直到它因某些其他原因再次发生(我从未设法诊断)。
其次,我发现声明性代码令人困惑,缺乏灵活性,难以重新考虑因素。在实例化期间传递不同的配置并不是直截了当的,因此同一个工厂可以用于测试数据的不同变化,这意味着我不得不重复代码,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
需要添加到工厂object
列表 - 听起来微不足道但是当你有一些代码生成数据时,这是一个可靠的错误。作为开发人员,您必须多次通过多个工厂才能了解控制流程。生成代码将在声明性主体之间传播,直到你用完这些技巧,然后其余的将进入后代或变得非常复杂。对我来说一个常见的例子是三个相互依赖的模型(例如,父子类别结构或数据集/属性/实体)作为另一个相互依赖的对象三元组的外键(例如,模型,参数值等,引用到其他模型的参数值)。这些类型的结构中的一些,特别是嵌套时,很快变得无法控制。
我意识到这并不是出于factory_boy的精神,而是把所有东西都放到了后代,解决了所有这些问题。我可以传入参数,因此同一个工厂可以满足我所有的复合模型测试数据要求,并且不会重复任何代码。创建的顺序很容易立即,明显和完全可靠,而不是依赖于混乱的继承和覆盖链,并受到一些错误。交互是显而易见的,因此您不需要消化整个事物来添加一些功能,并且不同的功能区域被分组在后代if子句中。无需排除工作变量,您可以在工厂代码的持续时间内引用它们。单元测试代码是简化的,因为描述功能在参数名称而不是Factory类名称中 - 因此您使用Meta.exclude
之类的调用而不是WhateverFactory(options__create_xyz=True, options__create_abc=True..
来创建数据。这样就可以很好地划分责任分工。