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
|
||||
|
||||
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,14 +843,15 @@ class TransactionTestCase(SimpleTestCase):
|
|||
try:
|
||||
self._fixture_teardown()
|
||||
super(TransactionTestCase, self)._post_teardown()
|
||||
# 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
|
||||
# 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.
|
||||
for conn in connections.all():
|
||||
conn.close()
|
||||
if self._should_reload_connections():
|
||||
# Some DB cursors include SQL statements as part of cursor
|
||||
# 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.
|
||||
for conn in connections.all():
|
||||
conn.close()
|
||||
finally:
|
||||
if self.available_apps is not None:
|
||||
apps.unset_available_apps()
|
||||
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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
|
||||
|
|
|
@ -2,12 +2,15 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import connection
|
||||
from django.db.utils import DatabaseError
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
from django.test import TransactionTestCase, skipUnlessDBFeature
|
||||
|
||||
from .models import Reporter, Article
|
||||
|
||||
|
||||
class IntrospectionTests(TestCase):
|
||||
class IntrospectionTests(TransactionTestCase):
|
||||
|
||||
available_apps = ['introspection']
|
||||
|
||||
def test_table_names(self):
|
||||
tl = connection.introspection.table_names()
|
||||
self.assertEqual(tl, sorted(tl))
|
||||
|
|
|
@ -5,7 +5,7 @@ from operator import attrgetter
|
|||
from django.core.exceptions import FieldError
|
||||
from django.core.management import call_command
|
||||
from django.db import connection
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils import six
|
||||
|
||||
|
@ -379,7 +379,9 @@ class ModelInheritanceTests(TestCase):
|
|||
s.titles.all(), [])
|
||||
|
||||
|
||||
class InheritanceSameModelNameTests(TestCase):
|
||||
class InheritanceSameModelNameTests(TransactionTestCase):
|
||||
|
||||
available_apps = ['model_inheritance']
|
||||
|
||||
def setUp(self):
|
||||
# The Title model has distinct accessors for both
|
||||
|
@ -402,14 +404,19 @@ class InheritanceSameModelNameTests(TestCase):
|
|||
INSTALLED_APPS={'append': ['model_inheritance.same_model_name']}):
|
||||
call_command('migrate', verbosity=0)
|
||||
from .same_model_name.models import Copy
|
||||
copy = self.title.attached_same_model_name_copy_set.create(
|
||||
content='The Web framework for perfectionists with deadlines.',
|
||||
url='http://www.djangoproject.com/',
|
||||
title='Django Rocks'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.title.attached_same_model_name_copy_set.create(
|
||||
content='The Web framework for perfectionists with deadlines.',
|
||||
url='http://www.djangoproject.com/',
|
||||
title='Django Rocks'
|
||||
), Copy.objects.get(
|
||||
copy,
|
||||
Copy.objects.get(
|
||||
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):
|
||||
# The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
|
||||
|
|
Loading…
Reference in New Issue