【问题标题】:Tests fails with TransactionTestCase and pytestTransactionTestCase 和 pytest 测试失败
【发布时间】:2016-11-02 16:10:20
【问题描述】:

我的单元测试和 django 管理事务的方式存在问题。

在我的代码中我有一个函数:

def send():
    autocommit = transaction.set_autocommit(False)
    try:
         # stuff
    finally:
         transaction.rollback()
         transaction.set_autocommit(autocommit)

在我的测试中我有:

class MyTest(TransactionTestCase):
    def test_send(self):
        send()

我遇到的问题是我的test_send 成功通过,但没有通过其他测试的 80%。

其他测试的事务似乎失败了

顺便说一句,我正在使用 py.test 运行我的测试

编辑: 为了让我在运行测试时更清楚 myapp.test.test_module.py 它运行良好,所有 3 个测试都通过了,但是当我运行所有测试时,大部分都失败了,将尝试生成一个测试应用程序

我的所有测试都通过 django 的默认测试运行程序通过

编辑2: 这是测试此问题的最小示例:

class ManagementTestCase(TransactionTestCase):

    def test_transfer_ubl(self, MockExact):
        pass

class TestTestCase(TestCase):

     def test_1_user(self):
         get_user_model().objects.get(username="admin")
         self.assertEqual(get_user_model().objects.all().count(), 1)

请记住,有一个添加“管理员”用户的数据迁移(TestTestCase 单独成功,但在之前运行 ManagmentTestCase 时不会成功)

似乎自动提交与它无关。

【问题讨论】:

  • 这个 send() 方法从何而来?请发布一个调用 send() 后失败的测试示例
  • 我写了 send 方法,其他测试都失败了,因为当他们计数对象时,它们的隔离被破坏了
  • 对不起,如果我的评论不清楚。发送方法是视图的一部分吗?部分测试代码?并且还请添加一个与这个一起执行时失败的测试样本
  • 听起来您在测试用例之间存在依赖关系,并且依赖于 django 运行程序为您提供的guaranteed ordering...如果您按照 django 运行程序重新排序,py.test 是否有效?
  • @PeterBrittain 如果我将我的事务测试用例作为最后一次测试,一切正常,但如果我在下次启动测试时使用 --reuse-db,它们会失败

标签: python django pytest django-1.8


【解决方案1】:

对于那些仍在寻找解决方案的人,serialized_rollback 选项是一个不错的选择:

class ManagementTestCase(TransactionTestCase):
    serialized_rollback = True

    def test_transfer_ubl(self, MockExact):
        pass

class TestTestCase(TestCase):

     def test_1_user(self):
         get_user_model().objects.get(username="admin")
         self.assertEqual(get_user_model().objects.all().count(), 1)

来自docs

Django 可以通过在TestCase 或TransactionTestCase 的主体中将serialized_rollback 选项设置为True 来为每个测试用例重新加载该数据,但请注意,这会减慢测试套件的速度大约 3 倍。

很遗憾,pytest-django 仍然缺少此功能。

【讨论】:

    【解决方案2】:

    在send 函数中包含autocommit = transaction.set_autocommit(False) 感觉不对。在这里禁用事务大概是出于测试目的,但经验法则是将测试逻辑保留在代码之外。

    正如@Alasdair 所指出的,django docs 声明“出于性能原因,Django 的 TestCase 类还将每个测试包装在事务中。”

    从您的问题中不清楚您是否正在测试特定的数据库事务逻辑,如果是这种情况,那么@Alasdair 使用TransactionTestCase 的答案就是要走的路。

    否则,从send 函数内的stuff 周围删除事务上下文切换应该会有所帮助。

    既然你提到pytest 作为你的测试运行器,我也建议使用 pytest。 Pytest-django 插件具有很好的功能,例如使用 markers 选择性地将一些测试设置为需要事务。

    pytest.mark.django_db(transaction=False)
    

    如果安装插件太多,那么您可以滚动自己的transaction manage 夹具。喜欢

      @pytest.fixture
      def no_transaction(request):
          autocommit = transaction.set_autocommit(False)
          def rollback():
              transaction.rollback()
              transaction.set_autocommit(True)
          request.addfinalizer(rollback)
    

    您的test_send 将需要no_transaction 夹具。

    def test_send(no_transaction):
        send()
    

    【讨论】:

    • 我使用 autocommit = False + transaction.rollback 因为我不希望 send() 中的某些调用将模型保存到数据库,我希望数据库不受影响,这与测试无关逻辑,我尝试使用 pytest.mark 没有成功,将再试一次
    • 我确实使用了 pytest.mark.django_db(transaction=False) 但现在看来数据库在测试过程中被清空了,这导致后来的测试中出现其他错误
    • 你会为每个测试获得一个全新的干净的数据库,如果这不是你想要的,你可以显式使用pytest-django.readthedocs.io/en/latest/…中的keepdb选项
    【解决方案3】:

    TestCase 类将测试包装在两个原子块中。因此,如果您从TestCase 继承,则不能使用transaction.set_autocommit() 或transaction.rollback()。

    正如文档所说,如果您正在测试特定的数据库事务行为,则应使用 TransactionTestCase。

    【讨论】:

    • 我做了,但它破坏了我的大部分其他测试
    • 将你的测试类分成两个类。 TransactionTestCase 用于 test_send 方法,TestCase 用于其他方法。
    • 我做到了,我有 300 多个测试
    • 进行多少测试并不重要。不能在 TestCase 类中使用 send()。
    猜你喜欢
    • 2016-06-02
    • 2015-11-30
    • 2019-09-14
    • 2018-04-05
    • 2021-02-06
    • 2015-02-03
    • 1970-01-01
    • 2020-03-12
    • 2020-04-08
    相关资源
    最近更新 更多