【问题标题】:Nose test case fails to set up with "Transaction managed block ended with pending COMMIT/ROLLBACK"Nose 测试用例无法设置“事务管理块以挂起的 COMMIT/ROLLBACK 结束”
【发布时间】:2016-06-23 03:12:21
【问题描述】:

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

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

为了解决这个问题,我们在DATABASES['geo'] 设置中添加了'TEST_MIRROR': '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'] 配置会导致此问题没有意义,但确实如此。

我该如何解决这个问题?

【问题讨论】:

  • 你尝试过使用标准的 django 测试运行器吗?

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


【解决方案1】:

好吧,我花了很多时间调试,但我发现了问题......

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

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

supports_transactions 做了以下事情:

  1. 创建一个新表
  2. 提交
  3. 在该表中插入一个值
  4. 回滚
  5. 选择该表中的行数
  6. 放下桌子
  7. 提交
  8. 如果表中的行数为 0,则返回 True(表示回滚成功,因此必须支持事务)。

这不安全。这是非常危险的。没有两个进程或服务器可以同时针对同一个数据库运行此操作。如果两个或多个进程同时运行此函数,充其量,一个会返回True,而另一个会引发异常。在最坏的情况下,所有人都会引发异常。

在我的例子中,因为我们有这么多的测试用例,大多数时候进程都在避免同时执行connections_support_transactions,但是当它们执行时,它会导致一小部分随机失败,每个失败都是不同的时间。

正如@kmmbvnr 指出的那样,一种可能的解决方案是使用SimpleTestCase 而不是FastFixtureTestCase。但是,这对我们来说不是一个选项,因为我们的整个测试基础架构取决于 FastFixtureTestCase 对我们其余数据库的作用。因此,我使用以下代码行仅针对静态共享数据库覆盖了supports_transactions,并且错误消失了:

connections['geo'].features.supports_transactions = True

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-25
    • 2012-04-14
    • 1970-01-01
    • 2020-05-23
    • 2016-10-04
    • 2017-05-19
    • 2016-09-18
    • 2012-11-11
    相关资源
    最近更新 更多