#python #django #factory-boy
#python #django #factory-boy
Вопрос:
Я пишу тесты для большого приложения 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 .клиент возвращает None
вместо ожидаемого Tenant
экземпляра.
Здесь задействовано довольно много фабрик / моделей, но компоновка относительно проста.
User
(django по умолчанию) и Profile
модель связаны через OneToOneField, которое (после некоторых проблем) представлено UserFactory
и ProfileFactory
@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
ниже представлено как a SubFactory
на приведенном ProfileFactory
выше.
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
с a 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)
Комментарии:
1. Добавьте код OrderFactory
2. Хороший улов, я исправил свой пример — спасибо!
3. Дикое предположение: попробуйте
order.refresh_from_db()
перед утверждениями. Кроме того, существуют ли какие-либо пользовательскиеsave
методы в этих моделях? Постгенерация (вашаUser.profile
) включает в себя вызовinstance.save()
перед завершением. Делайте таблицы дляProfile
чего-либо иTenant
храните что-либо / что вы ожидаете?4. Я не могу избавиться от ощущения, что что-то
UserFactory.profile
не так (да, я знаю, что ошибка, похоже , связана сProfileFactor.tenant
). Проходит ли тест, если вы используете ранее созданный экземпляр пользователя:order = OrderFactory(user = user)
?5. Спасибо за ваш ответ! — в том числе
.refresh_from_db()
до того, как утверждение не прошло — order = OrderFactory(user = user) проходит! — У Order есть пользовательский метод сохранения для установки некоторых полей даты при сохранении, но отключение этого метода ничего не изменило — Ваши комментарии о UserProfile привели меня к дальнейшему изучениюProfile
объекта, существует метод post_save (включенный в мой пост выше), который создалProfile
приUser
создании. Я учел этот сигнал, используя@factory.django.mute_signals
декоратор какUserFactory
для, так и дляProfileFactory
Ответ №1:
Как я упоминал в комментарии, я обнаружил источник проблемы: post-save
метод, связанный с UserProfile
(я включил код в свой пост).
Этот post-save
метод создал Profile
при User
создании. Я учел этот сигнал, используя @factory.django.mute_signals
декоратор как UserFactory
для, так и ProfileFactory
для .
Я предполагал, что любые вызовы Order.user
будут вызывать UserFactory
то, что уже было вложено в декоратор, но это предположение не оказалось ошибочным. Тесты прошли только тогда, когда я применил декорированный к тому OrderFactory
же.
Таким образом @factory.django.mute_signals
, декоратор следует использовать не только на фабриках, на которые влияют эти сигналы, но и на любой фабрике, которая использует эти фабрики в качестве SubFactory
!