【问题标题】:FactoryBoy - nested factories / max depth?FactoryBoy - 嵌套工厂/最大深度?
【发布时间】:2019-04-18 14:36:01
【问题描述】:

我正在为一个大型 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')

Order 链接到User,但它的许多方法调用其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)

【问题讨论】:

  • 添加 OrderFactory 代码
  • 很好,我修正了我的例子 - 谢谢!
  • 大胆猜测:在断言之前尝试order.refresh_from_db()。这些模型中还有任何自定义的save 方法吗? PostGeneration(您的User.profile)包括在完成之前对instance.save() 的调用。 ProfileTenant 的表格是否包含您所期望的任何内容/内容?
  • 我无法摆脱UserFactory.profile 出现问题的感觉(是的,我知道错误似乎ProfileFactor.tenant 有关)。如果您使用之前创建的用户实例:order = OrderFactory(user = user),测试是否通过?
  • 感谢您的回复! - 包括断言未通过之前的.refresh_from_db() - order = OrderFactory(user = user) 确实通过了! - 订单有一个自定义保存方法可以在保存时设置一些日期字段,但禁用此方法并没有改变任何东西 - 您关于 UserProfile 的 cmets 确实引导我进一步检查 Profile 对象,存在一个 post_save 方法(包含在我上面的帖子中) 在 User 创建时创建了 Profile。我通过在UserFactoryProfileFactory 上使用@factory.django.mute_signals 装饰器来解释这个信号

标签: python django factory-boy


【解决方案1】:

正如我在评论中提到的,我发现了问题的根源:链接到UserProfilepost-save 方法(我已在我的帖子中包含代码)。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-14
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多