【问题标题】:Django tests work using SQLite but not MySQLDjango 测试使用 SQLite 而不是 MySQL
【发布时间】:2023-03-03 08:32:20
【问题描述】:

我为我的 Django 应用程序编写的测试在我使用 SQLite 的初始开发过程中运行良好。现在我已经准备好部署了,我已经设置了一个 MySQL 服务器(因为这就是我将要部署到的服务器),但是现在我的一些测试失败了。

最后,当我手动测试功能时,失败的测试不会失败。

会发生什么?

我没有做任何不寻常的事情,所有的视图都会做一些数据库恶作剧并返回响应。没有任何与时间相关的东西(没有线程或任何东西)。

所有测试都继承自 django.test.TestCase,我没有使用任何固定装置。

这是一个失败的测试示例。

class BaseTest(TestCase):
    def setUp(self):
        super(BaseTest, self).setUp()

        self.userCreds = dict(username='test', password='a')

        # Create an admin user
        admin = models.User.objects.create_superuser(
            email='', username='admin', password='a')

        # Create a user and grant them a licence
        user = models.User.objects.create_user(
            email='some@address.com', first_name="Mister",
            last_name="Testy", **self.userCreds)

        profile = models.getProfileFor(user)
        node = profile.createNode(
            '12345', 'acomputer', 'auser',
            'user@email.com', '0456 987 123')

        self.node = node

class TestClientUIViews(BaseTest):
    def test_toggleActive(self):
        url = reverse('toggleActive') + '?nodeId=%s' % self.node.nodeId

        self.assertFalse(self.node.active)

        # This should fail because only authenticated users can toggle a node active
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 403)
        self.assertFalse(self.node.active)

        # Login and make sure visiting the url toggles the active state
        self.client.login(**self.userCreds)
        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertTrue(self.node.active)

        resp = self.client.get(url)
        self.assertEqual(resp.status_code, 200)
        self.assertFalse(self.node.active)

这是模型的样子:

class Node(models.Model):
    @property
    def active(self):
        '''
        Activation state gets explictly tracked in its own table but is
        exposed as a property for the sake of convenience
        '''
        activations = NodeActivation.objects \
            .filter(node=self) \
            .order_by('-datetime')

        try:
            return activations[0].active
        except IndexError:
            return False

    @active.setter
    def active(self, state):
        if self.active != state:
            NodeActivation.objects.create(node=self, active=state)

class NodeActivation(models.Model):
    node = models.ForeignKey("Node")
    datetime = models.DateTimeField(default=datetimeM.datetime.now)
    active = models.BooleanField(default=False)

我的本​​地 MySQL 是 5.5.19(所以它使用 InnoDB),但我在使用 5.1.56 的部署服务器上遇到了同样的故障。无论存储引擎如何,测试都会失败。

正如我在开头提到的,如果我切换回使用 SQLite 数据库,所有测试都会返回通过。

【问题讨论】:

  • 您的测试从哪个测试基类派生而来?例如TestCase。请用一个简单的失败测试示例更新您的问题,并附上类结构。
  • 测试失败时的错误信息是什么?你在测试中使用了一些夹具还是一些工厂?
  • @AustinPhillips - 我添加了来自失败测试之一的代码。它最后失败了,最后一个断言(评论)
  • @François - 错误消息只是“AssertionError: True is not False”。我没有使用固定装置或工厂。然而,我已经覆盖了 setUp 方法来用一些非常基本的初始数据填充数据库。
  • @Hamish self.node 是什么?如果您期望测试的最后一行是 assertFalse 正确,而上面的相同测试应该是 assertTrue

标签: django unit-testing


【解决方案1】:

现在揭示了更多实际代码,我将提供以下假设来说明此测试失败的原因。

NodeActivation 模型不正确。 datetime 字段应该是:

datetime = models.DateTimeField(auto_now=True)

在模型定义中使用 datetime.datetime.now() 只会被评估一次。

setter 每次创建新的NodeActivation 记录时,都会使用相同的日期/时间创建记录。即首次评估NodeActivation 模型的日期/时间。

你的 getter 只返回一个结果。但由于两个激活记录具有相同的日期/时间,因此排序可能取决于数据库后端。测试结束时,您的数据库中将有两条NodeActivation 记录,返回哪一条是不确定的。

【讨论】:

  • 我使用 default=datetime.datetime.now 的原因(注意:我传递函数不是调用函数的结果 - django 在创建新记录时调用它)是因为我很难让时区在数据库和 python 之间匹配。解决这个问题是一个 TODO,但这不是测试失败的原因(我只是检查以确保 :-))。另外我认为如果是这样,它也会以 SQLite 作为后备数据库失败。
  • @Hamish 对不起,你是对的。我错过了default 是一个函数。即便如此,如果您的测试运行得很快,并且激活记录的 datetime 字段最终具有相同的时间戳,那么您如何保证 getter 中的结果顺序?尝试在 getter 中将 -id 添加到您的 order_by 中。更好的是,在每个步骤中转储 NodeActivation 表的全部内容以验证您的假设。
  • 当然!很好的想法@Austin,谢谢。就是这样,非常感谢!
【解决方案2】:

通过将 Node 模型类的 active 属性更改为:

@property
def active(self):
    '''
    Activation state gets explictly tracked in its own table but is
    exposed as a property for the sake of convenience
    '''
    activations = NodeActivation.objects \
        .filter(node=self) \
        .order_by('-id')

    try:
        return activations[0].active
    except IndexError:
        return False

问题消失了。

注意对order_by 调用的更改。

记录的创建速度如此之快,以至于按日期时间排序不是确定性的,因此出现了不稳定的行为。而且我猜 SQLite 只是比 MySQL 慢,这就是为什么将它用作后备数据库时没有问题的原因。

注意:感谢Austin Phillips 的提示(查看他的回答中的 cmets)

【讨论】:

  • 我认为你应该仍然拥有.order_by('-datetime', '-id'),只是为了向其他人展示你想要按日期排序的对象。
  • 附言。虽然提供您自己的答案并接受它是完全可以的,但如果已经有答案解决了您的问题,最好接受现有答案之一。
【解决方案3】:

我在从 SQLite 到 PostgreSQL 时遇到了类似的问题(不确定是否相同),使用的设置方法与您对初始数据库对象的设置方法类似。

示例::

def setUp(self):
   user = User.objects.create_user(username='john', password='abbeyRd', email='john@thebeatles.com')
   user.save()

我发现这在 SQLite 上可以正常工作,但在 PostgreSQL 上我遇到了数据库连接失败(它仍然会像你说的那样构建数据库,但是如果没有连接错误就无法运行测试)。

事实证明,解决方法是按如下方式运行您的设置方法:

@classmethod
def setUpTestData(cls):
    user = User.objects.create_user(username='john', password='abbeyRd', email='john@thebeatles.com')
    user.save()

我不记得为什么这个有效而另一个无效的确切原因,但如果您使用 setUp(self) 并且如果您使用 @,它与尝试重新连接每个测试的数据库连接有关987654324@ 它仅在实例化类时连接,但我可能没有完美的细节。我只知道它有效!

希望对您有所帮助,不确定它是否解决了您的确切问题,但我花了好几个小时才弄清楚,所以我希望它对您和将来的其他人有所帮助!

【讨论】:

    猜你喜欢
    • 2011-09-15
    • 1970-01-01
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-10
    • 2010-12-22
    • 1970-01-01
    相关资源
    最近更新 更多