Nose测试用例无法设置"事务管理块以待处理的COMMIT / ROLLBACK"

时间:2016-03-08 23:05:57

标签: python django unit-testing transactions django-nose

我们有一个大型的Django项目,大约有10,000个Django + Nose单元测试。我们很少在代码库中使用事务。可能99%的代码库都不使用事务。该项目在Django 1.5.8和Django Nose 1.4.1上。 (是的,我知道这已经很老了。我们目前正在进行一个项目更新到Django 1.6的18个月,但还没有完成。所以,如果我的问题的解决方案是"升级Django,"我需要一种方法来修补它,因为现在这个问题正在发生,并且在我们完成升级Django之前还需要几个月。)

我们今天遇到了一个我们以前从未见过的新错误。我们添加了一个新数据库(以及必需的DATABASES['geo']设置),其中包含应用程序未更新的大型静态数据集。它是一个只读数据库,恰好存在于MySQL中。与我们所有其他数据库一样,Django Nose在每次测试运行的开始(结束)开始创建新数据库的测试副本(并销毁所述测试数据库)。这导致了许多问题,包括磁盘空间问题和浪费时间的问题,但测试确实已经运行并通过了。

要解决此问题,我们在'TEST_MIRROR': 'geo'设置中添加了DATABASES['geo']。这就是头痛开始的地方。只是这种变化导致我们的测试用例的一小部分随机部分在每次测试运行中失败:

<nose.suite.ContextSuite context=TestFacebookApiVersion>:setup
<nose.suite.ContextSuite context=RegisterPageTests>:setup
<nose.suite.ContextSuite context=CommonCeleryTasks>:setup
<nose.suite.ContextSuite context=CommonCeleryTestTasks>:setup
<nose.suite.ContextSuite context=S3PublishTestCase>:setup
<nose.suite.ContextSuite context=TestCEP>:setup
<nose.suite.ContextSuite context=AdbInvitesJsonTests>:setup

每个测试用例的错误和堆栈跟踪都是相同的:

Transaction managed block ended with pending COMMIT/ROLLBACK
Traceback (most recent call last):
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/suite.py", line 209, in run
    self.setUp()
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/suite.py", line 292, in setUp
    self.setupContext(ancestor)
  File "/var/lib/jenkins/workspace/my_workspace /my_project/lib/python2.7/site-packages/nose/suite.py", line 315, in setupContext
    try_run(context, names)
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/nose/util.py", line 471, in try_run
    return func()
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django_nose/testcases.py", line 43, in setUpClass
    if not test.testcases.connections_support_transactions():
  File "/var/lib/jenkins/workspace/my_workspace /my_project/lib/python2.7/site-packages/django/test/testcases.py", line 827, in connections_support_transactions
    for conn in connections.all())
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/test/testcases.py", line 827, in <genexpr>
    for conn in connections.all())
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/utils/functional.py", line 45, in __get__
    res = instance.__dict__[self.func.__name__] = self.func(instance)
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/db/backends/__init__.py", line 455, in supports_transactions
    self.connection.leave_transaction_management()
  File "/var/lib/jenkins/workspace/my_workspace/my_project/lib/python2.7/site-packages/django/db/backends/__init__.py", line 138, in leave_transaction_management
    "Transaction managed block ended with pending COMMIT/ROLLBACK")

而且,更糟糕的是,每一小部分失败的测试用例都是不同的。以下是我第二次运行它时的失败:

<nose.suite.ContextSuite context=BaseTemplateContainerTests>:setup
<nose.suite.ContextSuite context=MessageServiceNotifierTests>:setup
<nose.suite.ContextSuite context=TestFormatShortAddress>:setup
<nose.suite.ContextSuite context=RevisionableTestCase>:setup
<nose.suite.ContextSuite context=TestSoaHelpers>:setup
<nose.suite.ContextSuite context=TestDateUtils>:setup

等等。

正如您从堆栈跟踪中看到的那样,执行甚至无法实现我们的源代码。在我们的测试用例开始执行之前,它在Django Nose源代码中失败了。而且,这只是我们测试的一小部分。其他9,600多个单元测试都通过了绚丽的色彩。

我不知所措。我没有故意创建任何交易,对我来说,将'TEST_MIRROR': 'geo'添加到DATABASES['geo']配置会导致此问题并不合理,但确实如此。

我该如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

嗯,它花了我很多调试,但我发现了问题......

我们的10,000次测试需要很长时间才能运行,除非我们在并行进程中运行它们。因此,我们使用一种工具将鼻子测试分成20个并行进程并分组运行测试。 (这仍然需要大约20分钟才能完成,但它比近两个小时更好。)

我们使用扩展FastFixtureTestCase的{​​{1}}。在每个测试用例开始时,TransactionTestCase调用FastFixtureTestCase。该函数遍历所有django.test.testcases: connections_support_transactions()个连接,并在每个连接上调用DATABASES。我的错误是假设supports_transactions应该是一个固有的安全操作。不是。

supports_transactions执行以下操作:

  1. 创建新表
  2. 提交
  3. 在该表中插入值
  4. 回滚
  5. 选择该表中的行数
  6. 放下桌子
  7. 提交
  8. 如果表中的行数为0(表示回滚成功,则必须支持事务),则返回supports_transactions
  9. 这不安全。这非常危险。没有两个进程或服务器可以同时针对同一个数据库运行它。如果两个或多个进程同时运行此函数,则最多只返回True,其他进程将引发异常。最糟糕的是,所有人都会提出异常。

    在我的情况下,因为我们有很多测试用例,大多数情况下这些进程同时避免执行True,但是当它们这样做时,它会导致一小部分随机故障每次都不同。

    一种可能的解决方案是使用connections_support_transactions代替SimpleTestCase,正如@kmmbvnr所指出的那样。但是,这不是我们的选择,因为我们的整个测试基础架构取决于FastFixtureTestCase对其他数据库的作用。所以,相反,我仅使用以下代码行覆盖FastFixtureTestCase静态共享数据库,错误就消失了:

    supports_transactions