请问任何人解释TestCase类和TransactionTestCase类之间的区别。我已阅读该文档但其唯一的说法是TestCase在数据库事务中运行测试并使用回滚来“撤消”数据库中的测试,如果需要手动管理测试中的事务,则需要使用django.test.TransactionTestCase 。
请您通过示例帮助我了解实际差异吗? 我只是想知道TestCase失败的条件是什么?还有回滚是自动发生还是我们必须写回滚声明?
请帮帮我
答案 0 :(得分:8)
TestCase
和TransactionTestCase
之间的主要区别在于TestCase
始终用atomic()
块包围测试。来自documentation:
在两个嵌套的atomic()块中包含测试:一个用于整个类,一个用于每个测试
现在假设您有一个方法,如果它没有包含在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()就是一个明显的例子:
在自动提交模式下使用select_for_update()在支持SELECT的后端上评估查询集... FOR UPDATE是一个TransactionManagementError错误
来自TransactionTestCase documentation:
使用TestCase类,您无法测试事务中是否正在执行代码块,这在使用select_for_update()
时是必需的
如果我们查看select_for_update()
的文档,我们会看到警告:
尽管select_for_update()通常在自动提交模式下失败,但由于TestCase会自动将每个测试包装在一个事务中,因此即使在atomic()块之外调用TestCase中的select_for_update()也会(可能意外地)通过而不会引发TransactionManagementError。要正确测试select_for_update(),您应该使用TransactionTestCase。
希望它有所帮助!
答案 1 :(得分:0)
我想在这里发布一些示例和Django代码,以便您了解TransactionTestCase和TestCase的工作方式。
TransactionTestCase和TestCase都继承自SimpleTestCase。区别:
在运行测试时,TestCase将检查当前数据库是否支持事务功能。如果为True,将创建一个事务,并且所有测试代码现在都在“事务块”下。在测试结束时,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
第二个会顺利通过。