FactoryBoy - 嵌套工厂 / 最大深度?

8
我正在为一个大型的Django应用编写测试,作为这个过程的一部分,我正在逐步为Django项目中不同应用的所有模型创建工厂。
然而,我在FactoryBoy中遇到了一些令人困惑的行为,它几乎看起来像SubFactories有一个最大深度,超过该深度将不生成任何实例。
当我尝试运行以下测试时发生错误:
    def test_subfactories(self):
        """ Verify that the factory is able to initialize """
        user = UserFactory()
        self.assertTrue(user)
        self.assertTrue(user.profile)
        self.assertTrue(user.profile.tenant)

        order = OrderFactory()
        self.assertTrue(order)
        self.assertTrue(order.user.profile.tenant)

最后一行会失败(AssertionError: None is not true),通过调试器运行这个测试可以发现,确实order.user.profile.tenant返回的是None而不是期望的Tenant实例。
虽然涉及到了许多工厂/模型,但布局相对简单。 User (django默认)和Profile模型通过OneToOneField链接在一起,经过一些麻烦之后,由UserFactoryProfileFactory表示。
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = yuza_models.Profile
        django_get_or_create = ('user',)

    user = factory.SubFactory('yuza.factories.UserFactory')
    birth_date = factory.Faker('date_of_birth')
    street = factory.Faker('street_name')
    house_number = factory.Faker('building_number')
    city = factory.Faker('city')
    country = factory.Faker('country')
    avatar_file = factory.django.ImageField(color='blue')
    tenant = factory.SubFactory(TenantFactory)

@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):

    class Meta:
        model = auth_models.User

    username = factory.Sequence(lambda n: "user_%d" % n)
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')

    email = factory.Faker('email')
    is_staff = False
    is_superuser = False
    is_active = True
    last_login = factory.LazyFunction(timezone.now)

    @factory.post_generation
    def profile(self, create, extracted):
        if not create:
            return
        if extracted is None:
            ProfileFactory(user=self)


下面的TenantFactory在上文的ProfileFactory中被表示为一个SubFactory
class TenantFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = elearning_models.Tenant

    name = factory.Faker('company')
    slug = factory.LazyAttribute(lambda obj: text.slugify(obj.name))
    name_manager = factory.Faker('name')
    title_manager = factory.Faker('job')
    street = factory.Faker('street_name')
    house_number = factory.Faker('building_number')
    house_number_addition = factory.Faker('secondary_address')

OrderUser 相关联,但其许多方法调用了 self.user.profile.tenant 的字段。

class OrderFactory(factory.DjangoModelFactory):
    class Meta:
        model = Order

    user = factory.SubFactory(UserFactory)
    order_date = factory.LazyFunction(timezone.now)
    price = factory.LazyFunction(lambda: Decimal(random.uniform(1, 100)))
    site_tenant = factory.SubFactory(TenantFactory)
    no_tax = fuzzy.FuzzyChoice([True, False])

大部分测试中的断言都通过了,所有单独的工厂都能够从其直接的外键关系中初始化获取值。但是,一旦工厂/模型彼此相距三步,调用将返回None而不是预期的Tenant实例。

因为我在FactoryBoy文档中找不到任何关于这种行为的参考,所以这可能是我的问题,但是到目前为止,我还无法确定它的起源。有人知道我哪里错了吗?

post_save方法

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        profile = Profile.objects.create(user=instance)
        resume = profile.get_resume()
        resume.initialize()


post_save.connect(create_user_profile, sender=User)

1
添加OrderFactory代码。 - gdef_
好的,我修改了我的示例 - 谢谢! - Jasper
2
猜测一下:在断言之前尝试使用 order.refresh_from_db()。此外,这些模型中是否有任何自定义的 save 方法?PostGeneration(您的 User.profile)在完成之前包括对 instance.save() 的调用。ProfileTenant 表中是否保存了任何预期的内容? - CoffeeBasedLifeform
1
我总觉得UserFactory.profile有些不对劲(是的,我知道问题似乎出在ProfileFactor.tenant)。如果您使用早期创建的用户实例:order = OrderFactory(user = user),测试是否通过? - CoffeeBasedLifeform
感谢您的回复!
  • 在断言之前包括 .refresh_from_db() 没有通过
  • order = OrderFactory(user = user) 通过了!
  • Order 有一个自定义的保存方法来设置一些日期字段,但禁用此方法并没有改变任何内容
  • 您关于 UserProfile 的评论引导我进一步检查了 Profile 对象,存在一个 post_save 方法(在我的上面的帖子中包含)来创建一个 ProfileUser 创建时。我通过在 UserFactoryProfileFactory 上使用 @factory.django.mute_signals 装饰器来考虑这个信号。
- Jasper
当我将这个装饰器应用到OrderFactory上时,测试通过了!我原以为对Order.user的任何调用都会触发已经包含装饰器的UserFactory,但显然这并不够。 - Jasper
1个回答

0

正如我在评论中提到的那样,我已经发现了问题的根源:与UserProfile链接的post-save方法(我在帖子中已经包括了代码)。

这个post-save方法在创建User时创建了一个Profile。我通过在UserFactoryProfileFactory上使用@factory.django.mute_signals装饰器来处理这个信号。

我曾经假定对于Order.user的任何调用都会触发已经用装饰器包装的UserFactory,但这个假设被证明是错误的。只有当我也将装饰器应用于OrderFactory时,测试才能通过。

因此,@factory.django.mute_signals装饰器不仅应该用于受这些信号影响的工厂,而且还应该用于任何使用这些工厂作为SubFactory的工厂!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接