Commit da9fe5c7 authored by Thomas Chaumeny's avatar Thomas Chaumeny Committed by Tim Graham
Browse files

Fixed #20392 -- Added TestCase.setUpTestData()

Each TestCase is also now wrapped in a class-wide transaction.
parent dee4d23f
Loading
Loading
Loading
Loading
+59 −15
Original line number Diff line number Diff line
@@ -786,10 +786,11 @@ class TransactionTestCase(SimpleTestCase):

            raise

    def _databases_names(self, include_mirrors=True):
    @classmethod
    def _databases_names(cls, include_mirrors=True):
        # If the test case has a multi_db=True flag, act on all databases,
        # including mirrors or not. Otherwise, just on the default DB.
        if getattr(self, 'multi_db', False):
        if getattr(cls, 'multi_db', False):
            return [alias for alias in connections
                    if include_mirrors or not connections[alias].settings_dict['TEST']['MIRROR']]
        else:
@@ -829,6 +830,9 @@ class TransactionTestCase(SimpleTestCase):
                call_command('loaddata', *self.fixtures,
                             **{'verbosity': 0, 'database': db_name})

    def _should_reload_connections(self):
        return True

    def _post_teardown(self):
        """Performs any post-test things. This includes:

@@ -839,12 +843,13 @@ class TransactionTestCase(SimpleTestCase):
        try:
            self._fixture_teardown()
            super(TransactionTestCase, self)._post_teardown()
            if self._should_reload_connections():
                # Some DB cursors include SQL statements as part of cursor
            # creation. If you have a test that does rollback, the effect of
            # these statements is lost, which can effect the operation of
                # creation. If you have a test that does a rollback, the effect
                # of these statements is lost, which can affect the operation of
                # tests (e.g., losing a timezone setting causing objects to be
            # created with the wrong time). To make sure this doesn't happen,
            # get a clean connection at the start of every test.
                # created with the wrong time). To make sure this doesn't
                # happen, get a clean connection at the start of every test.
                for conn in connections.all():
                    conn.close()
        finally:
@@ -899,15 +904,54 @@ def connections_support_transactions():

class TestCase(TransactionTestCase):
    """
    Does basically the same as TransactionTestCase, but surrounds every test
    with a transaction, monkey-patches the real transaction management routines
    to do nothing, and rollsback the test transaction at the end of the test.
    You have to use TransactionTestCase, if you need transaction management
    inside a test.
    Similar to TransactionTestCase, but uses `transaction.atomic()` to achieve
    test isolation.

    In most situation, TestCase should be prefered to TransactionTestCase as
    it allows faster execution. However, there are some situations where using
    TransactionTestCase might be necessary (e.g. testing some transactional
    behavior).

    On database backends with no transaction support, TestCase behaves as
    TransactionTestCase.
    """

    @classmethod
    def setUpClass(cls):
        super(TestCase, cls).setUpClass()
        if not connections_support_transactions():
            return
        cls.cls_atomics = {}
        for db_name in cls._databases_names():
            cls.cls_atomics[db_name] = transaction.atomic(using=db_name)
            cls.cls_atomics[db_name].__enter__()
        cls.setUpTestData()

    @classmethod
    def tearDownClass(cls):
        if connections_support_transactions():
            for db_name in reversed(cls._databases_names()):
                transaction.set_rollback(True, using=db_name)
                cls.cls_atomics[db_name].__exit__(None, None, None)
            for conn in connections.all():
                conn.close()
        super(TestCase, cls).tearDownClass()

    @classmethod
    def setUpTestData(cls):
        """Load initial data for the TestCase"""
        pass

    def _should_reload_connections(self):
        if connections_support_transactions():
            return False
        return super(TestCase, self)._should_reload_connections()

    def _fixture_setup(self):
        if not connections_support_transactions():
            # If the backend does not support transactions, we should reload
            # class data before each test
            self.setUpTestData()
            return super(TestCase, self)._fixture_setup()

        assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances'
+12 −0
Original line number Diff line number Diff line
@@ -507,6 +507,10 @@ Tests
* The :func:`~django.test.override_settings` decorator can now affect the
  master router in :setting:`DATABASE_ROUTERS`.

* Added the ability to setup test data at the class level using
  :meth:`TestCase.setUpTestData() <django.test.TestCase.setUpTestData>`. Using
  this technique can speed up the tests as compared to using ``setUp()``.

Validators
^^^^^^^^^^

@@ -743,6 +747,14 @@ The new package is available `on Github`_ and on PyPI.

.. _on GitHub: https://github.com/django/django-formtools/

Database connection reloading between tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Django previously closed database connections between each test within a
``TestCase``. This is no longer the case as Django now wraps the whole
``TestCase`` within a transaction. If some of your tests relied on the old
behavior, you should have them inherit from ``TransactionTestCase`` instead.

Miscellaneous
~~~~~~~~~~~~~

+33 −1
Original line number Diff line number Diff line
@@ -691,13 +691,45 @@ additions, including:

* Automatic loading of fixtures.

* Wraps each test in a transaction.
* Wraps the tests within two nested ``atomic`` blocks: one for the whole class
  and one for each test.

* Creates a TestClient instance.

* Django-specific assertions for testing for things like redirection and form
  errors.

.. classmethod:: TestCase.setUpTestData()

    .. versionadded:: 1.8

    The class-level ``atomic`` block described above allows the creation of
    initial data at the class level, once for the whole ``TestCase``. This
    technique allows for faster tests as compared to using ``setUp()``.

    For example::

        from django.test import TestCase

        class MyTests(TestCase):
            @classmethod
            def setUpTestData(cls):
                # Set up data for the whole TestCase
                cls.foo = Foo.objects.create(bar="Test")
                ...

            def test1(self):
                # Some test using self.foo
                ...

            def test2(self):
                # Some other test using self.foo
                ...

    Note that if the tests are run on a database with no transaction support
    (for instance, MySQL with the MyISAM engine), ``setUpTestData()`` will be
    called before each test, negating the speed benefits.

.. warning::

    If you want to test some specific database transaction behavior, you should
+11 −8
Original line number Diff line number Diff line
@@ -345,13 +345,14 @@ class ParameterHandlingTest(TestCase):
# Unfortunately, the following tests would be a good test to run on all
# backends, but it breaks MySQL hard. Until #13711 is fixed, it can't be run
# everywhere (although it would be an effective test of #13711).
class LongNameTest(TestCase):
class LongNameTest(TransactionTestCase):
    """Long primary keys and model names can result in a sequence name
    that exceeds the database limits, which will result in truncation
    on certain databases (e.g., Postgres). The backend needs to use
    the correct sequence name in last_insert_id and other places, so
    check it is. Refs #8901.
    """
    available_apps = ['backends']

    def test_sequence_name_length_limits_create(self):
        """Test creation of model with long name and long pk name doesn't error. Ref #8901"""
@@ -465,7 +466,9 @@ class EscapingChecksDebug(EscapingChecks):
    pass


class BackendTestCase(TestCase):
class BackendTestCase(TransactionTestCase):

    available_apps = ['backends']

    def create_squares_with_executemany(self, args):
        self.create_squares(args, 'format', True)
@@ -653,9 +656,8 @@ class BackendTestCase(TestCase):
        """
        Test the documented API of connection.queries.
        """
        reset_queries()

        with connection.cursor() as cursor:
            reset_queries()
            cursor.execute("SELECT 1" + connection.features.bare_select_suffix)
        self.assertEqual(1, len(connection.queries))

@@ -823,7 +825,9 @@ class FkConstraintsTests(TransactionTestCase):
            transaction.set_rollback(True)


class ThreadTests(TestCase):
class ThreadTests(TransactionTestCase):

    available_apps = ['backends']

    def test_default_connection_thread_local(self):
        """
@@ -987,9 +991,7 @@ class MySQLPKZeroTests(TestCase):
            models.Square.objects.create(id=0, root=0, square=1)


class DBConstraintTestCase(TransactionTestCase):

    available_apps = ['backends']
class DBConstraintTestCase(TestCase):

    def test_can_reference_existent(self):
        obj = models.Object.objects.create()
@@ -1066,6 +1068,7 @@ class DBTestSettingsRenamedTests(IgnoreAllDeprecationWarningsMixin, TestCase):

    @classmethod
    def setUpClass(cls):
        super(DBTestSettingsRenamedTests, cls).setUpClass()
        # Silence "UserWarning: Overriding setting DATABASES can lead to
        # unexpected behavior."
        cls.warning_classes.append(UserWarning)
+2 −2
Original line number Diff line number Diff line
from django.test import TestCase
from django.test import TransactionTestCase
from django.core import management

from .models import Book


class TestNoInitialDataLoading(TestCase):
class TestNoInitialDataLoading(TransactionTestCase):
    """
    Apps with migrations should ignore initial data. This test can be removed
    in Django 1.9 when migrations become required and initial data is no longer
Loading