小言_互联网的博客

Django单元测试类和测试数据回滚

503人阅读  评论(0)

Django单元测试每个测试用例中写入数据库的测试数据在用例执行完后都会被撤销,这是通过事务回滚或者manage.py flush实现的。

TestCase与TransactionTestCase都是继承自SimpleTestCase,两者主要的区别在于:

  • TestCase在测试开始时,判断当前连接的数据库是否支持事务特性,如支持,则开启事务操作;在测试结束时,同样判断是否支持事务特性,如支持,执行事务回滚,然后关闭所有链接。具体setUpClass与tearDownClass方法如下
@classmethod
    def setUpClass(cls):
        super(TestCase, cls).setUpClass()
        if not connections_support_transactions():  # 事务支持判断
            return
        cls.cls_atomics = cls._enter_atomics()  # 开启事务,TestCase中测试代码均处于此事务Block中

        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与TestCase不同,在此测试类中并不开启事务块,测试结束时通过进行Fush操作清空数据。此类没有重写SimpleTestCase的setUp和tearDown方法,只修改了_post_teardown等如下:
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)

在事务方面的区别使得:使用TestCase时,如果被测试代码中出现必须在事务块中执行的代码,则会抛出异常,如官方举例的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)

第一个TestCase会抛出异常:

AssertionError: TransactionManagementError not raised

第二个TTC会通过测试。

小结

  • 使用TestCase,相当于后续代码都会处于一个外层事务的Block内执行,因此测试者不能测试必须运行在事务Block中的代码 (For instance, you cannot test that a block of code is executing within a transaction, as is required when using select_for_update())
  • TestCase中,最终事务需要进行回滚,因此如果在测试代码中进行了conn.close()一类的操作将会引起异常
  • TransactionTestCase不开启事务,并且通过测试结束时Flush DB的方案来还原干净环境

ref: https://blog.csdn.net/BDuck2014/article/details/86521755


转载:https://blog.csdn.net/pushiqiang/article/details/101451205
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场