Factory-boy / Django - 工厂实例不反映对模型实例的更改

Factory-boy / Django - Factory instance not reflecting changes to model instance

我正在为我正在开发的网站编写测试,我用 factoryboy 工厂对象表示模型。

但是,我 运行 的一些行为让我感到有些困惑,我想知道这里是否有人愿意向我解释一下

我正在运行进行包含以下模型的测试:

STATUS = (
    ('CALCULATING'),
    ('PENDING'),
    ('BUSY'),
    ('SUCCESS'),
    ('FAILED')
)


class SchoolImport(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    status = models.CharField(
        verbose_name=_('status'), choices=STATUS,
        max_length=50, default='CALCULATING'
    )

为此我创建了以下工厂。如您所见,status 设置为其默认值,我发现这比随机选择值

更真实
class SchoolImportFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.SchoolImport

    status = 'CALCULATING'
    school = factory.SubFactory(SchoolFactory)

    @factory.lazy_attribute
    def date(self):
        return timezone.now() - datetime.timedelta(days=10)

下面您将看到正在测试的函数的(简化)版本以及测试本身。 (我目前已经注释掉了笔记本电脑上的所有其他代码,因此您在下面看到的函数是准确的表示)

它的要点是该函数接收一个 id 值,它将使用该值从数据库中获取一个 SchoolImport 对象并更改其状态。该函数在 celery 中将是 运行,因此 return 什么都没有。

当我 运行 通过调试器进行此测试时,我可以看到值已正确更改。但是,当测试 运行 的最终断言失败时,因为 self.school_import.status 仍然等于 CALCULATING.


#app.utils.py
def process_template_objects(school_import_pk):
    school_import = models.SchoolImport.objects.get(id=import_file_pk)
    school_import.status = 'BUSY'
    school_import.save()



#app.tests.test_utils.py
class Test_process_template_objects_function(TestCase):

    def setUp(self):
        self.school = SchoolFactory()
        self.school_import = SchoolImportFactory(
            school=self.school
        )

    def test_function_alters_school_import_status(self):
        self.assertEqual(
            self.school_import.status, 'CALCULATING'
        )
        utils.process_template_objects(self.school_import.id)
        self.assertNotEqual(
            self.school_import.status, 'CALCULATING'
        )

当我 运行 通过调试器(在断言失败之前有一个断点)进行此测试并且 运行 SchoolImport.objects.get(id=self.school_import.id).status 它 return 正确 BUSY 值。

因此,尽管 FactoryInstance 所表示的对象正在正确更新,但更改并未反映在工厂实例本身中。

虽然我意识到我可能在这里做错了什么/遇到了预期的行为,但我想知道使用 factoryboy fget 编写测试的人如何绕过这种行为,或者是否有办法 'refresh' factoryboy 实例来反映对模型实例的更改。

问题来自于这样一个事实,即在 process_template_objects 中,您使用的 SchoolImport 对象实例与测试中的实例不同。

如果你运行:

a = models.SchoolImport.objects.get(pk=1)
b = models.SchoolImport.objects.get(pk=2)

assert a == b  # True: both refer to the same object in the database
assert a is b  # False: different Python objects, each with its own memory

a.status = 'SUCCESS'
a.save()
assert a.status == 'SUCCESS'  # True: it was indeed changed in this Python object
assert b.status == 'SUCCESS'  # False: the 'b' object hasn't seen the change

为了解决这个问题,您应该在调用 process_template_objects:

后从数据库中重新获取实例
utils.process_template_objects(self.school_import.id)
self.school_import.refresh_from_db()

请参阅 https://docs.djangoproject.com/en/2.2/ref/models/instances/#refreshing-objects-from-database 以获得更详细的解释!

如果您从模型实例中删除一个字段,再次访问它会从数据库中重新加载该值。

obj = MyModel.objects.first()
del obj.field
obj.field  # Loads the field from the database

https://docs.djangoproject.com/en/2.2/ref/models/instances/#refreshing-objects-from-database