Python:Factory Boy 生成对象创建时指定长度的列表

Ste*_*ven 7 python unit-testing factory-boy

我正在尝试使用 Factoryboy 在创建时指定长度的对象中创建一个列表。

我可以创建列表,但是由于提供的长度/大小的惰性性质,每次尝试创建具有指定长度的列表都会导致问题。

这是我到目前为止所拥有的:

class FooFactory(factory.Factory):

    class Meta:
        model = command.Foo

    foo_uuid = factory.Faker("uuid4")
    bars = factory.List([
        factory.LazyAttribute(lambda o: BarFactory()
        for _ in range(3))
    ])
Run Code Online (Sandbox Code Playgroud)

这将创建一个包含 3 个随机条的列表。我尝试过使用 Params 和排除的组合,但由于 range 需要一个 Int,并且 int 直到稍后才会延迟加载,因此会导致错误。

我想要类似于如何使用 post_ Generation 生成一对多关系的东西,即。

foo = FooFactory(number_of_bars=5)
Run Code Online (Sandbox Code Playgroud)

有人有这样的运气吗?

Gab*_*lli 8

主要解决方案

为此需要两件事: 参数LazyAttribute (链接指向其文档,以获取更多详细信息)。

参数就像工厂属性,不会传递给将创建的实例。在这种情况下,它们提供了一种参数化 s 列表长度的方法Bar

但是为了使用参数来自定义工厂中的字段,我们需要访问self,即正在构建的实例。我们可以使用 来实现这一点LazyAttribute,这是一个带有一个参数的函数的声明:正在构建的对象。正是我们所需要的。

因此问题中的片段可以重写如下:

class FooFactory(factory.Factory):
    class Meta:
        model = command.Foo

    class Params:
        number_of_bars = 1

    foo_uuid = factory.Faker("uuid4")
    bars = factory.LazyAttribute(lambda self: [BarFactory()] * self.number_of_bars)
Run Code Online (Sandbox Code Playgroud)

并像这样使用:

foo = FooFactory(number_of_bars=3)
Run Code Online (Sandbox Code Playgroud)

如果number_of_bars未提供参数,则1使用默认值。

缺点

遗憾的是,我们在这里能做的事情有一些限制。

在定义另一个工厂时使用一个工厂的首选方式是 via SubFactory。这是首选的原因有两个:

  1. 它尊重父工厂使用的构建策略
  2. 它收集额外的关键字参数来定制子工厂

第一个意味着,如果我们过去SubFactory构建BarinFooFactoryFooFactory使用FooFactory.createor进行调用FooFactory.build,子Bar工厂将尊重这一点并使用相同的策略。总之,构建策略仅构建实例,而创建策略构建实例并将其保存到正在使用的持久存储中,例如数据库,因此尊重此选择很重要。请参阅文档 了解更多详细信息。

第二个意思是我们可以直接自定义Bar调用时的属性FooFactory。例如:

foo = FooFactory(bar__id=2)
Run Code Online (Sandbox Code Playgroud)

会将idof 的值bar设置foo为,2而不是Bar子工厂默认生成的值。

但我找不到使用方法SubFactory 动态长度Params。据我所知,无法在 FactoryBoy 期望的上下文中访问参数的值SubFactory。问题在于,允许我们访问正在构建的对象的声明总是期望返回最终值,而不是稍后调用另一个工厂。这意味着,在上面的例子中,如果我们改写:

class FooFactory(factory.Factory):
    # ... rest of the factory
    bars = factory.LazyAttribute(lambda self: [factory.SubFactory(BarFactory)] * self.number_of_bars)
Run Code Online (Sandbox Code Playgroud)

然后这样称呼它

foo = FooFactory(number_of_bars=3)
Run Code Online (Sandbox Code Playgroud)

将导致 afoo具有 3 的列表BarFactoryfoo.bars而不是 3 的列表Bar。使用SelfAttribute,这是一种引用正在构建的实例的另一个属性的方法,也不起作用,因为它不会在声明中的其余表达式之前进行评估,如下所示:

class FooFactory(factory.Factory):
    # ... rest of the factory
    bars = factory.List([factory.SubFactory(BarFactory)] * SelfAttribute("number_of_bars"))
Run Code Online (Sandbox Code Playgroud)

这就引发了TypeError: can't multiply sequence by non-int of type 'SelfAttribute'. 一种可能的解决方法是提前调用BarFactory并将其传递给FooFactory

number_of_bars = 3
bars = BarFactory.create_batch(number_of_bars)
foo = FooFactory(bars=bars)
Run Code Online (Sandbox Code Playgroud)

但这肯定没有那么好。

我最近发现的另一件事是RelatedFactoryList。但这仍然是实验性的,而且似乎没有办法访问参数。此外,由于它是在基础工厂之后生成的,因此如果实例构造函数需要该属性作为参数,它也可能不起作用。