Fixed #20392 -- Added TestCase.setUpTestData()
Each TestCase is also now wrapped in a class-wide transaction.
This commit is contained in:
parent
dee4d23f7e
commit
da9fe5c717
|
@ -786,10 +786,11 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
|
|
||||||
raise
|
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,
|
# If the test case has a multi_db=True flag, act on all databases,
|
||||||
# including mirrors or not. Otherwise, just on the default DB.
|
# 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
|
return [alias for alias in connections
|
||||||
if include_mirrors or not connections[alias].settings_dict['TEST']['MIRROR']]
|
if include_mirrors or not connections[alias].settings_dict['TEST']['MIRROR']]
|
||||||
else:
|
else:
|
||||||
|
@ -829,6 +830,9 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
call_command('loaddata', *self.fixtures,
|
call_command('loaddata', *self.fixtures,
|
||||||
**{'verbosity': 0, 'database': db_name})
|
**{'verbosity': 0, 'database': db_name})
|
||||||
|
|
||||||
|
def _should_reload_connections(self):
|
||||||
|
return True
|
||||||
|
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
"""Performs any post-test things. This includes:
|
"""Performs any post-test things. This includes:
|
||||||
|
|
||||||
|
@ -839,12 +843,13 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
try:
|
try:
|
||||||
self._fixture_teardown()
|
self._fixture_teardown()
|
||||||
super(TransactionTestCase, self)._post_teardown()
|
super(TransactionTestCase, self)._post_teardown()
|
||||||
|
if self._should_reload_connections():
|
||||||
# Some DB cursors include SQL statements as part of cursor
|
# Some DB cursors include SQL statements as part of cursor
|
||||||
# creation. If you have a test that does rollback, the effect of
|
# creation. If you have a test that does a rollback, the effect
|
||||||
# these statements is lost, which can effect the operation of
|
# of these statements is lost, which can affect the operation of
|
||||||
# tests (e.g., losing a timezone setting causing objects to be
|
# tests (e.g., losing a timezone setting causing objects to be
|
||||||
# created with the wrong time). To make sure this doesn't happen,
|
# created with the wrong time). To make sure this doesn't
|
||||||
# get a clean connection at the start of every test.
|
# happen, get a clean connection at the start of every test.
|
||||||
for conn in connections.all():
|
for conn in connections.all():
|
||||||
conn.close()
|
conn.close()
|
||||||
finally:
|
finally:
|
||||||
|
@ -899,15 +904,54 @@ def connections_support_transactions():
|
||||||
|
|
||||||
class TestCase(TransactionTestCase):
|
class TestCase(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Does basically the same as TransactionTestCase, but surrounds every test
|
Similar to TransactionTestCase, but uses `transaction.atomic()` to achieve
|
||||||
with a transaction, monkey-patches the real transaction management routines
|
test isolation.
|
||||||
to do nothing, and rollsback the test transaction at the end of the test.
|
|
||||||
You have to use TransactionTestCase, if you need transaction management
|
In most situation, TestCase should be prefered to TransactionTestCase as
|
||||||
inside a test.
|
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):
|
def _fixture_setup(self):
|
||||||
if not connections_support_transactions():
|
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()
|
return super(TestCase, self)._fixture_setup()
|
||||||
|
|
||||||
assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances'
|
assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances'
|
||||||
|
|
|
@ -507,6 +507,10 @@ Tests
|
||||||
* The :func:`~django.test.override_settings` decorator can now affect the
|
* The :func:`~django.test.override_settings` decorator can now affect the
|
||||||
master router in :setting:`DATABASE_ROUTERS`.
|
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
|
Validators
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -743,6 +747,14 @@ The new package is available `on Github`_ and on PyPI.
|
||||||
|
|
||||||
.. _on GitHub: https://github.com/django/django-formtools/
|
.. _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
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -691,13 +691,45 @@ additions, including:
|
||||||
|
|
||||||
* Automatic loading of fixtures.
|
* 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.
|
* Creates a TestClient instance.
|
||||||
|
|
||||||
* Django-specific assertions for testing for things like redirection and form
|
* Django-specific assertions for testing for things like redirection and form
|
||||||
errors.
|
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::
|
.. warning::
|
||||||
|
|
||||||
If you want to test some specific database transaction behavior, you should
|
If you want to test some specific database transaction behavior, you should
|
||||||
|
|
|
@ -345,13 +345,14 @@ class ParameterHandlingTest(TestCase):
|
||||||
# Unfortunately, the following tests would be a good test to run on all
|
# 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
|
# 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).
|
# 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
|
"""Long primary keys and model names can result in a sequence name
|
||||||
that exceeds the database limits, which will result in truncation
|
that exceeds the database limits, which will result in truncation
|
||||||
on certain databases (e.g., Postgres). The backend needs to use
|
on certain databases (e.g., Postgres). The backend needs to use
|
||||||
the correct sequence name in last_insert_id and other places, so
|
the correct sequence name in last_insert_id and other places, so
|
||||||
check it is. Refs #8901.
|
check it is. Refs #8901.
|
||||||
"""
|
"""
|
||||||
|
available_apps = ['backends']
|
||||||
|
|
||||||
def test_sequence_name_length_limits_create(self):
|
def test_sequence_name_length_limits_create(self):
|
||||||
"""Test creation of model with long name and long pk name doesn't error. Ref #8901"""
|
"""Test creation of model with long name and long pk name doesn't error. Ref #8901"""
|
||||||
|
@ -465,7 +466,9 @@ class EscapingChecksDebug(EscapingChecks):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BackendTestCase(TestCase):
|
class BackendTestCase(TransactionTestCase):
|
||||||
|
|
||||||
|
available_apps = ['backends']
|
||||||
|
|
||||||
def create_squares_with_executemany(self, args):
|
def create_squares_with_executemany(self, args):
|
||||||
self.create_squares(args, 'format', True)
|
self.create_squares(args, 'format', True)
|
||||||
|
@ -653,9 +656,8 @@ class BackendTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the documented API of connection.queries.
|
Test the documented API of connection.queries.
|
||||||
"""
|
"""
|
||||||
reset_queries()
|
|
||||||
|
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
|
reset_queries()
|
||||||
cursor.execute("SELECT 1" + connection.features.bare_select_suffix)
|
cursor.execute("SELECT 1" + connection.features.bare_select_suffix)
|
||||||
self.assertEqual(1, len(connection.queries))
|
self.assertEqual(1, len(connection.queries))
|
||||||
|
|
||||||
|
@ -823,7 +825,9 @@ class FkConstraintsTests(TransactionTestCase):
|
||||||
transaction.set_rollback(True)
|
transaction.set_rollback(True)
|
||||||
|
|
||||||
|
|
||||||
class ThreadTests(TestCase):
|
class ThreadTests(TransactionTestCase):
|
||||||
|
|
||||||
|
available_apps = ['backends']
|
||||||
|
|
||||||
def test_default_connection_thread_local(self):
|
def test_default_connection_thread_local(self):
|
||||||
"""
|
"""
|
||||||
|
@ -987,9 +991,7 @@ class MySQLPKZeroTests(TestCase):
|
||||||
models.Square.objects.create(id=0, root=0, square=1)
|
models.Square.objects.create(id=0, root=0, square=1)
|
||||||
|
|
||||||
|
|
||||||
class DBConstraintTestCase(TransactionTestCase):
|
class DBConstraintTestCase(TestCase):
|
||||||
|
|
||||||
available_apps = ['backends']
|
|
||||||
|
|
||||||
def test_can_reference_existent(self):
|
def test_can_reference_existent(self):
|
||||||
obj = models.Object.objects.create()
|
obj = models.Object.objects.create()
|
||||||
|
@ -1066,6 +1068,7 @@ class DBTestSettingsRenamedTests(IgnoreAllDeprecationWarningsMixin, TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
super(DBTestSettingsRenamedTests, cls).setUpClass()
|
||||||
# Silence "UserWarning: Overriding setting DATABASES can lead to
|
# Silence "UserWarning: Overriding setting DATABASES can lead to
|
||||||
# unexpected behavior."
|
# unexpected behavior."
|
||||||
cls.warning_classes.append(UserWarning)
|
cls.warning_classes.append(UserWarning)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from django.test import TestCase
|
from django.test import TransactionTestCase
|
||||||
from django.core import management
|
from django.core import management
|
||||||
|
|
||||||
from .models import Book
|
from .models import Book
|
||||||
|
|
||||||
|
|
||||||
class TestNoInitialDataLoading(TestCase):
|
class TestNoInitialDataLoading(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Apps with migrations should ignore initial data. This test can be removed
|
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
|
in Django 1.9 when migrations become required and initial data is no longer
|
||||||
|
|
|
@ -2,12 +2,15 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.utils import DatabaseError
|
from django.db.utils import DatabaseError
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import Reporter, Article
|
from .models import Reporter, Article
|
||||||
|
|
||||||
|
|
||||||
class IntrospectionTests(TestCase):
|
class IntrospectionTests(TransactionTestCase):
|
||||||
|
|
||||||
|
available_apps = ['introspection']
|
||||||
|
|
||||||
def test_table_names(self):
|
def test_table_names(self):
|
||||||
tl = connection.introspection.table_names()
|
tl = connection.introspection.table_names()
|
||||||
self.assertEqual(tl, sorted(tl))
|
self.assertEqual(tl, sorted(tl))
|
||||||
|
|
|
@ -5,7 +5,7 @@ from operator import attrgetter
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TestCase
|
from django.test import TestCase, TransactionTestCase
|
||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
@ -379,7 +379,9 @@ class ModelInheritanceTests(TestCase):
|
||||||
s.titles.all(), [])
|
s.titles.all(), [])
|
||||||
|
|
||||||
|
|
||||||
class InheritanceSameModelNameTests(TestCase):
|
class InheritanceSameModelNameTests(TransactionTestCase):
|
||||||
|
|
||||||
|
available_apps = ['model_inheritance']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# The Title model has distinct accessors for both
|
# The Title model has distinct accessors for both
|
||||||
|
@ -402,14 +404,19 @@ class InheritanceSameModelNameTests(TestCase):
|
||||||
INSTALLED_APPS={'append': ['model_inheritance.same_model_name']}):
|
INSTALLED_APPS={'append': ['model_inheritance.same_model_name']}):
|
||||||
call_command('migrate', verbosity=0)
|
call_command('migrate', verbosity=0)
|
||||||
from .same_model_name.models import Copy
|
from .same_model_name.models import Copy
|
||||||
self.assertEqual(
|
copy = self.title.attached_same_model_name_copy_set.create(
|
||||||
self.title.attached_same_model_name_copy_set.create(
|
|
||||||
content='The Web framework for perfectionists with deadlines.',
|
content='The Web framework for perfectionists with deadlines.',
|
||||||
url='http://www.djangoproject.com/',
|
url='http://www.djangoproject.com/',
|
||||||
title='Django Rocks'
|
title='Django Rocks'
|
||||||
), Copy.objects.get(
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
copy,
|
||||||
|
Copy.objects.get(
|
||||||
content='The Web framework for perfectionists with deadlines.',
|
content='The Web framework for perfectionists with deadlines.',
|
||||||
))
|
))
|
||||||
|
# We delete the copy manually so that it doesn't block the flush
|
||||||
|
# command under Oracle (which does not cascade deletions).
|
||||||
|
copy.delete()
|
||||||
|
|
||||||
def test_related_name_attribute_exists(self):
|
def test_related_name_attribute_exists(self):
|
||||||
# The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
|
# The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
|
||||||
|
|
Loading…
Reference in New Issue