【问题标题】:Django testing - InternalError: current transaction is aborted, commands ignored until end of transaction blockDjango 测试 - InternalError:当前事务被中止,命令被忽略,直到事务块结束
【发布时间】:2011-04-05 19:02:41
【问题描述】:

在我的测试中,我不仅测试完美情况,还特别测试边缘情况和错误情况。所以我想确保一些唯一性约束有效。

虽然我的测试和测试装置非常复杂,但我能够将问题追溯到以下示例,该示例不使用任何自定义模型。要重现该行为,只需将代码保存到 tests.py 并运行 django 测试运行程序。

from django.contrib.auth.models import User
from django.db import IntegrityError
from django.test import TransactionTestCase

class TransProblemTest(TransactionTestCase):
    def test_uniqueness1(self):
        User.objects.create_user(username='user1', email='user1@example.com', password='secret')
        self.assertRaises(IntegrityError, lambda :
            User.objects.create_user(username='user1', email='user1@example.com', password='secret'))

    def test_uniqueness2(self):
        User.objects.create_user(username='user1', email='user1@example.com', password='secret')
        self.assertRaises(IntegrityError, lambda :
            User.objects.create_user(username='user1', email='user1@example.com', password='secret'))

具有单个测试方法的测试类可以工作,但由于两个相同的方法实现而失败。 第一个测试抛出异常会破坏 Django 测试环境,使后面的所有测试都失败。

我将 Django 1.1 与 Ubuntu 10.04、Postgres 8.4 和 psycopg2 一起使用。

这个问题在 Django 1.2 中还存在吗?

这是一个已知的错误还是我遗漏了什么?

【问题讨论】:

    标签: django postgresql django-testing


    【解决方案1】:

    Django 有两种风格的TestCase:“普通”TestCaseTransactionTestCasedocumentation 有以下说一下两者的区别:

    TransactionTestCaseTestCase 是相同的,除了将数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力。 TransactionTestCase 在测试运行之前通过截断所有表并重新加载初始数据来重置数据库。 TransactionTestCase 可以调用 commit 和 rollback 并观察这些调用对数据库的影响。

    另一方面,TestCase 不会在测试开始时截断表并重新加载初始数据。相反,它将测试代码包含在一个数据库事务中,该事务在测试结束时回滚。

    您正在使用TransactionTestCase 执行这些测试。切换到普通的TestCase,您将看到问题消失如果您维护现有的测试代码。

    为什么会这样? TestCase 在事务块内执行测试方法。这意味着您的测试类中的每个测试方法都将在单独的事务中运行,而不是在同一个事务中运行。当断言(或者更确切地说是内部的lambda)引发错误时,它会随着事务而死。下一个测试方法在 new 事务中执行,因此您看不到您遇到的错误。

    但是,如果您要在 same 测试方法中添加另一个 相同的断言,您会再次看到错误:

    class TransProblemTest(django.test.TestCase):
        def test_uniqueness1(self):
            User.objects.create_user(username='user1', email='user1@example.com', password='secret')
            self.assertRaises(IntegrityError, lambda :
                User.objects.create_user(username='user1', email='user1@example.com', password='secret'))
            # Repeat the test condition.
            self.assertRaises(IntegrityError, lambda :
                User.objects.create_user(username='user1', email='user1@example.com', password='secret'))
    

    这是因为第一个断言将创建一个导致事务中止的错误而触发的。第二个因此无法执行。由于两个断言都发生在同一个测试方法中,因此与前一种情况不同,新事务并未启动。

    希望这会有所帮助。

    【讨论】:

    • 谢谢!这解释了幕后发生的事情。文档给人的印象是 TransactionTestCase 提供了比 TestCase 更多的隔离。但是由于一个(Django 中的错误?)它提供的隔离较少——一个测试会破坏整个测试环境。我是否可以在生产中期待类似的问题 - 我应该完全避免使用约束,因为遇到约束会破坏之后运行的代码,还是会自动提交并捕获异常docs.djangoproject.com/en/1.1/topics/db/transactions/… 工作? "autocommit":True 对测试环境没有帮助。
    • 这还不是全部,您可以在运行测试后从 TransactionTestCase 获得数据库中的新数据。
    【解决方案2】:

    我假设当你说“单一测试方法有效”时,你的意思是它失败了,引发了异常,但不会破坏测试环境。

    也就是说,您在 AutoCommit 关闭的情况下运行。在这种模式下,默认情况下共享数据库连接上的所有内容都是单个事务,失败需要通过 ROLLBACK 中止事务,然后才能启动新事务。如果可能,我建议打开 AutoCommit——除非您需要将多个写入操作封装到一个单元中,否则将其关闭是多余的。

    【讨论】:

    • “开启自动提交”是指DATABASE_OPTIONS = { "autocommit": True, },如docs.djangoproject.com/en/1.1/ref/databases/#ref-databases 中所述吗?听起来很有希望,所以我尝试设置它,但它没有任何效果 - 运行第一个测试方法后测试环境仍然损坏。使用 TransactionMiddleware docs.djangoproject.com/en/1.1/topics/db/transactions/… 也没有帮助。
    • 如果 AutoCommit 已打开,那么您将不会收到错误“InternalError:当前事务已中止,命令在事务块结束之前被忽略”,因为从来没有事务块。如果您仍然收到此错误,那么它没有打开。如果您遇到不同的错误,则 AutoCommit 不是您的解决方案,“事务已中止”错误是其他问题的副作用。
    猜你喜欢
    • 2011-01-13
    • 1970-01-01
    • 2013-08-04
    • 2011-12-06
    • 2013-10-25
    • 1970-01-01
    • 2014-07-04
    • 2012-05-11
    • 2016-12-11
    相关资源
    最近更新 更多