Ticket #20392: refactor_testcase_transactions.diff

File refactor_testcase_transactions.diff, 7.8 KB (added by Raphaël Barrois, 11 years ago)

Patch against commit 327e362

  • django/test/testcases.py

    commit 967d5501a6f0b166e78c471c80aabc117e12a914
    Author: Raphaël Barrois <raphael.barrois@polytechnique.org>
    Date:   Sat May 11 00:26:34 2013 +0200
    
        test.TestCase: Use a single transaction per class.
        
        And wrap each test_XXX in a atomic() call.
    
    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index a9fcc2b..d4bde8d 100644
    a b class TransactionTestCase(SimpleTestCase):  
    448448    # test case
    449449    reset_sequences = False
    450450
     451    # Subclasses can ask for connection closure after each test or each TestCase
     452    reset_connections_per_test = True
     453
    451454    def _pre_setup(self):
    452455        """Performs any pre-test setup. This includes:
    453456
    class TransactionTestCase(SimpleTestCase):  
    463466        self._urlconf_setup()
    464467        mail.outbox = []
    465468
    466     def _databases_names(self, include_mirrors=True):
     469    @classmethod
     470    def _databases_names(cls, include_mirrors=True):
    467471        # If the test case has a multi_db=True flag, act on all databases,
    468472        # including mirrors or not. Otherwise, just on the default DB.
    469         if getattr(self, 'multi_db', False):
     473        if getattr(cls, 'multi_db', False):
    470474            return [alias for alias in connections
    471475                    if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
    472476        else:
    class TransactionTestCase(SimpleTestCase):  
    519523        # be created with the wrong time).
    520524        # To make sure this doesn't happen, get a clean connection at the
    521525        # start of every test.
     526        if self.reset_connections_per_test:
     527            for conn in connections.all():
     528                conn.close()
     529
     530    @classmethod
     531    def tearDownClass(cls):
     532        super(TransactionTestCase, cls).tearDownClass()
     533
     534        # Ensure connections are closed, no matter what.
     535        # django.db.backends.BaseDatabaseWrapper.close() behaves properly
     536        # on already closed connections.
    522537        for conn in connections.all():
    523538            conn.close()
    524539
    class TestCase(TransactionTestCase):  
    818833    inside a test.
    819834    """
    820835
     836    # For savepoints to be useful, connections should only be closed at
     837    # tearDownClass().
     838    reset_connections_per_test = False
     839
    821840    def _fixture_setup(self):
    822841        if not connections_support_transactions():
    823842            return super(TestCase, self)._fixture_setup()
    class TestCase(TransactionTestCase):  
    828847        for db_name in self._databases_names():
    829848            self.atomics[db_name] = transaction.atomic(using=db_name)
    830849            self.atomics[db_name].__enter__()
    831         # Remove this when the legacy transaction management goes away.
    832         disable_transaction_methods()
    833850
    834851        for db in self._databases_names(include_mirrors=False):
    835852            if hasattr(self, 'fixtures'):
    class TestCase(TransactionTestCase):  
    845862        if not connections_support_transactions():
    846863            return super(TestCase, self)._fixture_teardown()
    847864
     865        for db_name in reversed(self._databases_names()):
     866            if connections[db_name].connection is None:
     867                # Already closed
     868                continue
     869
     870            # Hack to force a rollback: populate the exception fields
     871            self.atomics[db_name].__exit__(ValueError, "Rollback", None)
     872
     873    @classmethod
     874    def setUpClass(cls):
     875        if not connections_support_transactions():
     876            return super(TestCase, cls).setUpClass()
     877
     878        cls.class_atomics = {}
     879        for db_name in cls._databases_names(include_mirrors=False):
     880            cls.class_atomics[db_name] = transaction.atomic(using=db_name)
     881            cls.class_atomics[db_name].__enter__()
     882
     883        # Remove this when the legacy transaction management goes away.
     884        disable_transaction_methods()
     885
     886        return super(TestCase, cls).setUpClass()
     887
     888    @classmethod
     889    def tearDownClass(cls):
     890        if not connections_support_transactions():
     891            return super(TestCase, cls).tearDownClass()
     892
    848893        # Remove this when the legacy transaction management goes away.
    849894        restore_transaction_methods()
    850         for db_name in reversed(self._databases_names()):
    851             # Hack to force a rollback
    852             connections[db_name].needs_rollback = True
    853             self.atomics[db_name].__exit__(None, None, None)
     895
     896        for db_name in reversed(cls._databases_names()):
     897            if connections[db_name].connection is None:
     898                # Already closed
     899                continue
     900
     901            cls.class_atomics[db_name].__exit__(ValueError, "Rollback", None)
     902
     903        return super(TestCase, cls).tearDownClass()
    854904
    855905
    856906def _deferredSkip(condition, reason):
  • docs/topics/testing/overview.txt

    diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
    index 9228a07..272c976 100644
    a b additions, including:  
    987987
    988988* Automatic loading of fixtures.
    989989
    990 * Wraps each test in a transaction.
     990* Wraps each TestCase in a single transaction, and each test in a
     991  SAVEPOINT/ROLLBACK pair; this allows creating objects within
     992  :meth:`~django.test.TestCase.setUpClass` instead of
     993  :meth:`~django.test.TestCase.setUp`.
    991994
    992995* Creates a TestClient instance.
    993996
    additions, including:  
    9991002    The order in which tests are run has changed. See `Order in which tests are
    10001003    executed`_.
    10011004
     1005.. versionchanged:: 1.6
     1006
     1007    Prior to 1.6, each test ran inside a dedicated transaction.
     1008    A transaction is now opened at :meth:`~django.test.TestCase.setUpClass`,
     1009    and each test runs within a savepoint.
     1010
     1011    This allows model creation in :meth:`~django.test.TestCase.setUpClass`,
     1012    that will stay unaltered by each test's code.
     1013
    10021014``TestCase`` inherits from :class:`~django.test.TransactionTestCase`.
    10031015
    10041016.. _live-test-server:
  • tests/test_utils/tests.py

    diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
    index d72a482..a8a7665 100644
    a b class SkippingExtraTests(TestCase):  
    598598        pass
    599599
    600600
     601class AtomicSetUpClass1(TestCase):
     602    person_pk = 0
     603
     604    @classmethod
     605    def setUpClass(cls):
     606        super(AtomicSetUpClass1, cls).setUpClass()
     607        person = Person.objects.create(name='setUpClass1')
     608        if cls.person_pk == 0:
     609            cls.person_pk = person.pk
     610
     611    def test_setupclass_called(self):
     612        p = Person.objects.get(name='setUpClass1')
     613        self.assertEqual(self.person_pk, p.pk)
     614
     615    def test_setupclass_called_only_once(self):
     616        """Ensure the Person is created only once."""
     617        p = Person.objects.get(name='setUpClass1')
     618        self.assertEqual(self.person_pk, p.pk)
     619
     620    def test_other_setupclass_rolled_back(self):
     621        """
     622        Ensure the Person created by AtomicSetUpClass2 doesn't exist.
     623        """
     624        persons = list(Person.objects.all())
     625        self.assertEqual(1, len(persons))
     626        person = persons[0]
     627        self.assertEqual(self.person_pk, person.pk)
     628        self.assertEqual('setUpClass1', person.name)
     629
     630
     631class AtomicSetUpClass2(TestCase):
     632    """
     633    Second TestCase, to ensure that setUpClass is rolled back between TestCase.
     634    """
     635    person_pk = 0
     636    @classmethod
     637    def setUpClass(cls):
     638        super(AtomicSetUpClass2, cls).setUpClass()
     639        person = Person.objects.create(name='setUpClass2')
     640        if cls.person_pk == 0:
     641            cls.person_pk = person.pk
     642
     643    def test_other_setupclass_rolled_back(self):
     644        """
     645        Ensure the Person created by AtomicSetUpClass1 doesn't exist anymore.
     646        """
     647        persons = list(Person.objects.all())
     648        self.assertEqual(1, len(persons))
     649        person = persons[0]
     650        self.assertEqual(self.person_pk, person.pk)
     651        self.assertEqual('setUpClass2', person.name)
     652
     653
    601654class AssertRaisesMsgTest(SimpleTestCase):
    602655
    603656    def test_special_re_chars(self):
Back to Top