【问题标题】:Factory-boy / Django - Factory instance not reflecting changes to model instanceFactory-boy / Django - 工厂实例不反映模型实例的更改
【发布时间】:2019-12-22 02:50:18
【问题描述】:

我正在为我正在开发的网站编写测试,并使用 factoryboyFactory 对象来表示模型。

但是,我遇到了一些让我有些困惑的行为,我想知道这里是否有人会这么好心地向我解释一下

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

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 中运行,因此不返回任何内容。

当我通过调试器运行此测试时,我可以看到值已正确更改。但是,当测试运行其最终断言时,它会失败,因为 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 时,它确实返回了正确的BUSY 值。

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

虽然我意识到我可能在这里做错了/遇到了预期的行为,但我想知道使用 factoryboy f 编写测试的人如何绕过这种行为,或者是否有一种方法可以“刷新” factoryboy 实例以反映模型实例的更改。

【问题讨论】:

  • 我想知道是不是因为您在运行models.SchoolImport.objects.get(id=import_file_pk) 时创建了一个新的学校导入对象,如果您将学校导入对象本身传递给函数会怎样。像def process_template_objects(school_import): school_import.status = ‘BUSY’ school_import.save() 这样的东西。 get 中的参数没有意义,您使用的是import_file_pk,但函数中没有定义。

标签: python django django-testing factory-boy


【解决方案1】:

问题在于,在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

【讨论】:

    【解决方案2】:

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

    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

    【讨论】:

      猜你喜欢
      • 2017-01-02
      • 2021-08-23
      • 2012-10-09
      • 1970-01-01
      • 1970-01-01
      • 2011-01-21
      • 1970-01-01
      • 1970-01-01
      • 2017-01-21
      相关资源
      最近更新 更多