django 测试中 TestCase 和 TransactionTestCase 类 的区别

Difference between TestCase and TransactionTestCase classes in django test

请解释TestCaseclass和TransactionTestCaseclass的区别。我已经阅读了文档,但它只是说 TestCase 在数据库事务中运行测试并使用回滚来“撤消”数据库中的测试,如果您需要在测试中手动管理事务,则需要使用 django.test.TransactionTestCase.

请通过示例帮助我了解实际差异。 TestCase 在什么情况下会失败?回滚是自动发生还是我们必须编写代码来执行回滚?

TestCaseTransactionTestCase 之间的主要区别是 TestCase 始终用 atomic() 块包装测试。来自 documentation:

Wraps the tests within two nested atomic() blocks: one for the whole class and one for each test

现在假设您有一个方法,如果它没有包含在 atomic() 块中,则应该引发错误。您正在尝试为此编写测试:

def test_your_method_raises_error_without_atomic_block(self):
    with self.assertRaises(SomeError):
        your_method()

本次测试意外失败!原因是,您猜对了,TestCase 始终用 atomic() 块包装测试。因此,your_method() 不会引发错误,这就是此测试失败的原因。在这种情况下,您应该使用 TransactionTestCase 让您的测试通过。

select_for_update()就是一个明显的例子:

Evaluating a queryset with select_for_update() in autocommit mode on backends which support SELECT ... FOR UPDATE is a TransactionManagementError error

来自TransactionTestCase documentation:

with TestCase class, you cannot test that a block of code is executing within a transaction, as is required when using select_for_update()

如果我们查看 select_for_update() 的文档,我们会看到一条警告:

Although select_for_update() normally fails in autocommit mode, since TestCase automatically wraps each test in a transaction, calling select_for_update() in a TestCase even outside an atomic() block will (perhaps unexpectedly) pass without raising a TransactionManagementError. To properly test select_for_update() you should use TransactionTestCase.

希望对您有所帮助!

我想在这里 post 一些示例和 django 代码,以便您了解 TransactionTestCaseTestCase 是如何工作的。

TransactionTestCaseTestCase都继承自SimpleTestCase。差异:

  • 运行测试时,TestCase会检查当前数据库是否支持事务特性。如果为真,将创建一个事务,所有测试代码现在都在“事务块”下。在测试结束时,TestCase 将回滚所有内容以保持您的数据库干净。阅读下面的 setUp()tearDown() 函数:

     @classmethod
     def setUpClass(cls):
             super(TestCase, cls).setUpClass()
             if not connections_support_transactions():
                 return
             cls.cls_atomics = cls._enter_atomics()
    
             if cls.fixtures:
                 for db_name in cls._databases_names(include_mirrors=False):
                         try:
                             call_command('loaddata', *cls.fixtures, **{
                                 'verbosity': 0,
                                 'commit': False,
                                 'database': db_name,
                             })
                         except Exception:
                             cls._rollback_atomics(cls.cls_atomics)
                             raise
             cls.setUpTestData()
    
    
     @classmethod
     def tearDownClass(cls):
         if connections_support_transactions():
             cls._rollback_atomics(cls.cls_atomics)
             for conn in connections.all():
                 conn.close()
         super(TestCase, cls).tearDownClass()
    
  • TransactionTestCase,但是,不启动事务。它只是在完成所有测试后刷新数据库。

     def _post_teardown(self):
         try:
             self._fixture_teardown()
             super(TransactionTestCase, self)._post_teardown()
             if self._should_reload_connections():
                 for conn in connections.all():
                     conn.close()
         finally:
             if self.available_apps is not None:
                 apps.unset_available_apps()
                 setting_changed.send(sender=settings._wrapped.__class__,
                                      setting='INSTALLED_APPS',
                                      value=settings.INSTALLED_APPS,
                                      enter=False)
    
     def _fixture_teardown(self):
         for db_name in self._databases_names(include_mirrors=False):
             call_command('flush', verbosity=0, interactive=False,
                          database=db_name, reset_sequences=False,
                          allow_cascade=self.available_apps is not None,
                          inhibit_post_migrate=self.available_apps is not None)
    

现在使用官方文档中提到的 select_for_update() 的一些非常简单的示例:

    class SampleTestCase(TestCase):
            def setUp(self):
                Sample.objects.create(**{'field1': 'value1', 'field2': 'value2'})

            def test_difference_testcase(self):
                sample = Sample.objects.select_for_update().filter()
                print(sample)


    class SampleTransactionTestCase(TransactionTestCase):
        def setUp(self):
            Sample.objects.create(**{'field1': 'value1', 'field2': 'value2'})

        def test_difference_transactiontestcase(self):
            sample = Sample.objects.select_for_update().filter()
            print(sample)

第一个将提出:

AssertionError: TransactionManagementError not raised

第二个会顺利通过。