mirror of https://github.com/django/django.git
Fixed #20579 -- Improved TransactionTestCase.available_apps.
Also moved its documentation to the 'advanced' section. It doesn't belong to the 'overview'. Same for TransactionTestCase.reset_sequences. When available_apps is set, after a TransactionTestCase, the database is now totally empty. post_syncdb is fired at the beginning of the next TransactionTestCase. Refs #20483.
This commit is contained in:
parent
0938970491
commit
55cbd65985
|
@ -32,9 +32,10 @@ class Command(NoArgsCommand):
|
||||||
connection = connections[db]
|
connection = connections[db]
|
||||||
verbosity = int(options.get('verbosity'))
|
verbosity = int(options.get('verbosity'))
|
||||||
interactive = options.get('interactive')
|
interactive = options.get('interactive')
|
||||||
# 'reset_sequences' and 'allow_cascade' are stealth options
|
# The following are stealth options used by Django's internals.
|
||||||
reset_sequences = options.get('reset_sequences', True)
|
reset_sequences = options.get('reset_sequences', True)
|
||||||
allow_cascade = options.get('allow_cascade', False)
|
allow_cascade = options.get('allow_cascade', False)
|
||||||
|
inhibit_post_syncdb = options.get('inhibit_post_syncdb', False)
|
||||||
|
|
||||||
self.style = no_style()
|
self.style = no_style()
|
||||||
|
|
||||||
|
@ -75,16 +76,9 @@ Are you sure you want to do this?
|
||||||
"Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run.\n"
|
"Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run.\n"
|
||||||
"The full error: %s") % (connection.settings_dict['NAME'], e)
|
"The full error: %s") % (connection.settings_dict['NAME'], e)
|
||||||
six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2])
|
six.reraise(CommandError, CommandError(new_msg), sys.exc_info()[2])
|
||||||
# Emit the post sync signal. This allows individual
|
|
||||||
# applications to respond as if the database had been
|
if not inhibit_post_syncdb:
|
||||||
# sync'd from scratch.
|
self.emit_post_syncdb(verbosity, interactive, db)
|
||||||
all_models = []
|
|
||||||
for app in models.get_apps():
|
|
||||||
all_models.extend([
|
|
||||||
m for m in models.get_models(app, include_auto_created=True)
|
|
||||||
if router.allow_syncdb(db, m)
|
|
||||||
])
|
|
||||||
emit_post_sync_signal(set(all_models), verbosity, interactive, db)
|
|
||||||
|
|
||||||
# Reinstall the initial_data fixture.
|
# Reinstall the initial_data fixture.
|
||||||
if options.get('load_initial_data'):
|
if options.get('load_initial_data'):
|
||||||
|
@ -93,3 +87,15 @@ Are you sure you want to do this?
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.stdout.write("Flush cancelled.\n")
|
self.stdout.write("Flush cancelled.\n")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def emit_post_syncdb(verbosity, interactive, database):
|
||||||
|
# Emit the post sync signal. This allows individual applications to
|
||||||
|
# respond as if the database had been sync'd from scratch.
|
||||||
|
all_models = []
|
||||||
|
for app in models.get_apps():
|
||||||
|
all_models.extend([
|
||||||
|
m for m in models.get_models(app, include_auto_created=True)
|
||||||
|
if router.allow_syncdb(database, m)
|
||||||
|
])
|
||||||
|
emit_post_sync_signal(set(all_models), verbosity, interactive, database)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||||
from django.core.handlers.wsgi import WSGIHandler
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
|
from django.core.management.commands import flush
|
||||||
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
|
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
|
||||||
WSGIServerException)
|
WSGIServerException)
|
||||||
from django.core.urlresolvers import clear_url_caches, set_urlconf
|
from django.core.urlresolvers import clear_url_caches, set_urlconf
|
||||||
|
@ -196,8 +197,8 @@ class SimpleTestCase(ut2.TestCase):
|
||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
"""Performs any pre-test setup. This includes:
|
"""Performs any pre-test setup. This includes:
|
||||||
|
|
||||||
* If the Test Case class has a 'urls' member, replace the
|
* Creating a test client.
|
||||||
ROOT_URLCONF with it.
|
* If the class has a 'urls' attribute, replace ROOT_URLCONF with it.
|
||||||
* Clearing the mail test outbox.
|
* Clearing the mail test outbox.
|
||||||
"""
|
"""
|
||||||
self.client = self.client_class()
|
self.client = self.client_class()
|
||||||
|
@ -212,6 +213,10 @@ class SimpleTestCase(ut2.TestCase):
|
||||||
clear_url_caches()
|
clear_url_caches()
|
||||||
|
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
|
"""Performs any post-test things. This includes:
|
||||||
|
|
||||||
|
* Putting back the original ROOT_URLCONF if it was changed.
|
||||||
|
"""
|
||||||
self._urlconf_teardown()
|
self._urlconf_teardown()
|
||||||
|
|
||||||
def _urlconf_teardown(self):
|
def _urlconf_teardown(self):
|
||||||
|
@ -732,13 +737,17 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
def _pre_setup(self):
|
def _pre_setup(self):
|
||||||
"""Performs any pre-test setup. This includes:
|
"""Performs any pre-test setup. This includes:
|
||||||
|
|
||||||
* Flushing the database.
|
* If the class has an 'available_apps' attribute, restricting the app
|
||||||
* If the Test Case class has a 'fixtures' member, installing the
|
cache to these applications, then firing post_syncdb -- it must run
|
||||||
named fixtures.
|
with the correct set of applications for the test case.
|
||||||
|
* If the class has a 'fixtures' attribute, installing these fixtures.
|
||||||
"""
|
"""
|
||||||
super(TransactionTestCase, self)._pre_setup()
|
super(TransactionTestCase, self)._pre_setup()
|
||||||
if self.available_apps is not None:
|
if self.available_apps is not None:
|
||||||
cache.set_available_apps(self.available_apps)
|
cache.set_available_apps(self.available_apps)
|
||||||
|
for db_name in self._databases_names(include_mirrors=False):
|
||||||
|
flush.Command.emit_post_syncdb(
|
||||||
|
verbosity=0, interactive=False, database=db_name)
|
||||||
try:
|
try:
|
||||||
self._fixture_setup()
|
self._fixture_setup()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -782,9 +791,9 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
"""Performs any post-test things. This includes:
|
"""Performs any post-test things. This includes:
|
||||||
|
|
||||||
* Putting back the original ROOT_URLCONF if it was changed.
|
* Flushing the contents of the database, to leave a clean slate. If
|
||||||
* Force closing the connection, so that the next test gets
|
the class has an 'available_apps' attribute, post_syncdb isn't fired.
|
||||||
a clean cursor.
|
* Force-closing the connection, so the next test gets a clean cursor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._fixture_teardown()
|
self._fixture_teardown()
|
||||||
|
@ -801,12 +810,14 @@ class TransactionTestCase(SimpleTestCase):
|
||||||
cache.unset_available_apps()
|
cache.unset_available_apps()
|
||||||
|
|
||||||
def _fixture_teardown(self):
|
def _fixture_teardown(self):
|
||||||
# Allow TRUNCATE ... CASCADE when flushing only a subset of the apps
|
# Allow TRUNCATE ... CASCADE and don't emit the post_syncdb signal
|
||||||
allow_cascade = self.available_apps is not None
|
# when flushing only a subset of the apps
|
||||||
for db_name in self._databases_names(include_mirrors=False):
|
for db_name in self._databases_names(include_mirrors=False):
|
||||||
call_command('flush', verbosity=0, interactive=False,
|
call_command('flush', verbosity=0, interactive=False,
|
||||||
database=db_name, skip_validation=True,
|
database=db_name, skip_validation=True,
|
||||||
reset_sequences=False, allow_cascade=allow_cascade)
|
reset_sequences=False,
|
||||||
|
allow_cascade=self.available_apps is not None,
|
||||||
|
inhibit_post_syncdb=self.available_apps is not None)
|
||||||
|
|
||||||
def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
|
def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
|
||||||
items = six.moves.map(transform, qs)
|
items = six.moves.map(transform, qs)
|
||||||
|
|
|
@ -155,6 +155,80 @@ If there are any circular dependencies in the
|
||||||
:setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured``
|
:setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured``
|
||||||
exception will be raised.
|
exception will be raised.
|
||||||
|
|
||||||
|
Advanced features of ``TransactionTestCase``
|
||||||
|
============================================
|
||||||
|
|
||||||
|
.. currentmodule:: django.test
|
||||||
|
|
||||||
|
.. attribute:: TransactionTestCase.available_apps
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This attribute is a private API. It may be changed or removed without
|
||||||
|
a deprecation period in the future, for instance to accomodate changes
|
||||||
|
in application loading.
|
||||||
|
|
||||||
|
It's used to optimize Django's own test suite, which contains hundreds
|
||||||
|
of models but no relations between models in different applications.
|
||||||
|
|
||||||
|
By default, ``available_apps`` is set to ``None``. After each test, Django
|
||||||
|
calls :djadmin:`flush` to reset the database state. This empties all tables
|
||||||
|
and emits the :data:`~django.db.models.signals.post_syncdb` signal, which
|
||||||
|
re-creates one content type and three permissions for each model. This
|
||||||
|
operation gets expensive proportionally to the number of models.
|
||||||
|
|
||||||
|
Setting ``available_apps`` to a list of applications instructs Django to
|
||||||
|
behave as if only the models from these applications were available. The
|
||||||
|
behavior of ``TransactionTestCase`` changes as follows:
|
||||||
|
|
||||||
|
- :data:`~django.db.models.signals.post_syncdb` is fired before each
|
||||||
|
test to create the content types and permissions for each model in
|
||||||
|
available apps, in case they're missing.
|
||||||
|
- After each test, Django empties only tables corresponding to models in
|
||||||
|
available apps. However, at the database level, truncation may cascade to
|
||||||
|
related models in unavailable apps. Furthermore
|
||||||
|
:data:`~django.db.models.signals.post_syncdb` isn't fired; it will be
|
||||||
|
fired by the next ``TransactionTestCase``, after the correct set of
|
||||||
|
applications is selected.
|
||||||
|
|
||||||
|
Since the database isn't fully flushed, if a test creates instances of
|
||||||
|
models not included in ``available_apps``, they will leak and they may
|
||||||
|
cause unrelated tests to fail. Be careful with tests that use sessions;
|
||||||
|
the default session engine stores them in the database.
|
||||||
|
|
||||||
|
Since :data:`~django.db.models.signals.post_syncdb` isn't emitted after
|
||||||
|
flushing the database, its state after a ``TransactionTestCase`` isn't the
|
||||||
|
same as after a ``TestCase``: it's missing the rows created by listeners
|
||||||
|
to :data:`~django.db.models.signals.post_syncdb`. Considering the
|
||||||
|
:ref:`order in which tests are executed <order-of-tests>`, this isn't an
|
||||||
|
issue, provided either all ``TransactionTestCase`` in a given test suite
|
||||||
|
declare ``available_apps``, or none of them.
|
||||||
|
|
||||||
|
``available_apps`` is mandatory in Django's own test suite.
|
||||||
|
|
||||||
|
.. attribute:: TransactionTestCase.reset_sequences
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
Setting ``reset_sequences = True`` on a ``TransactionTestCase`` will make
|
||||||
|
sure sequences are always reset before the test run::
|
||||||
|
|
||||||
|
class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase):
|
||||||
|
reset_sequences = True
|
||||||
|
|
||||||
|
def test_animal_pk(self):
|
||||||
|
lion = Animal.objects.create(name="lion", sound="roar")
|
||||||
|
# lion.pk is guaranteed to always be 1
|
||||||
|
self.assertEqual(lion.pk, 1)
|
||||||
|
|
||||||
|
Unless you are explicitly testing primary keys sequence numbers, it is
|
||||||
|
recommended that you do not hard code primary key values in tests.
|
||||||
|
|
||||||
|
Using ``reset_sequences = True`` will slow down the test, since the primary
|
||||||
|
key reset is an relatively expensive database operation.
|
||||||
|
|
||||||
Running tests outside the test runner
|
Running tests outside the test runner
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,8 @@ advanced settings.
|
||||||
|
|
||||||
The :ref:`advanced multi-db testing topics <topics-testing-advanced-multidb>`.
|
The :ref:`advanced multi-db testing topics <topics-testing-advanced-multidb>`.
|
||||||
|
|
||||||
|
.. _order-of-tests:
|
||||||
|
|
||||||
Order in which tests are executed
|
Order in which tests are executed
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
@ -908,8 +910,8 @@ TransactionTestCase
|
||||||
|
|
||||||
.. class:: TransactionTestCase()
|
.. class:: TransactionTestCase()
|
||||||
|
|
||||||
Django ``TestCase`` classes make use of database transaction facilities, if
|
Django's ``TestCase`` class (described below) makes use of database transaction
|
||||||
available, to speed up the process of resetting the database to a known state
|
facilities to speed up the process of resetting the database to a known state
|
||||||
at the beginning of each test. A consequence of this, however, is that the
|
at the beginning of each test. A consequence of this, however, is that the
|
||||||
effects of transaction commit and rollback cannot be tested by a Django
|
effects of transaction commit and rollback cannot be tested by a Django
|
||||||
``TestCase`` class. If your test requires testing of such transactional
|
``TestCase`` class. If your test requires testing of such transactional
|
||||||
|
@ -927,9 +929,9 @@ to test the effects of commit and rollback:
|
||||||
Instead, it encloses the test code in a database transaction that is rolled
|
Instead, it encloses the test code in a database transaction that is rolled
|
||||||
back at the end of the test. Both explicit commits like
|
back at the end of the test. Both explicit commits like
|
||||||
``transaction.commit()`` and implicit ones that may be caused by
|
``transaction.commit()`` and implicit ones that may be caused by
|
||||||
``Model.save()`` are replaced with a ``nop`` operation. This guarantees that
|
``transaction.atomic()`` are replaced with a ``nop`` operation. This
|
||||||
the rollback at the end of the test restores the database to its initial
|
guarantees that the rollback at the end of the test restores the database to
|
||||||
state.
|
its initial state.
|
||||||
|
|
||||||
When running on a database that does not support rollback (e.g. MySQL with the
|
When running on a database that does not support rollback (e.g. MySQL with the
|
||||||
MyISAM storage engine), ``TestCase`` falls back to initializing the database
|
MyISAM storage engine), ``TestCase`` falls back to initializing the database
|
||||||
|
@ -940,22 +942,21 @@ to test the effects of commit and rollback:
|
||||||
While ``commit`` and ``rollback`` operations still *appear* to work when
|
While ``commit`` and ``rollback`` operations still *appear* to work when
|
||||||
used in ``TestCase``, no actual commit or rollback will be performed by the
|
used in ``TestCase``, no actual commit or rollback will be performed by the
|
||||||
database. This can cause your tests to pass or fail unexpectedly. Always
|
database. This can cause your tests to pass or fail unexpectedly. Always
|
||||||
use ``TransactionalTestCase`` when testing transactional behavior.
|
use ``TransactionTestCase`` when testing transactional behavior.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
.. versionchanged:: 1.5
|
||||||
|
|
||||||
Prior to 1.5, ``TransactionTestCase`` flushed the database tables *before*
|
Prior to 1.5, :class:`~django.test.TransactionTestCase` flushed the
|
||||||
each test. In Django 1.5, this is instead done *after* the test has been run.
|
database tables *before* each test. In Django 1.5, this is instead done
|
||||||
|
*after* the test has been run.
|
||||||
|
|
||||||
When the flush took place before the test, it was guaranteed that primary
|
When the flush took place before the test, it was guaranteed that primary
|
||||||
key values started at one in :class:`~django.test.TransactionTestCase`
|
key values started at one in :class:`~django.test.TransactionTestCase`
|
||||||
tests.
|
tests.
|
||||||
|
|
||||||
Tests should not depend on this behavior, but for legacy tests that do, the
|
Tests should not depend on this behavior, but for legacy tests that do,
|
||||||
:attr:`~TransactionTestCase.reset_sequences` attribute can be used until
|
the :attr:`~TransactionTestCase.reset_sequences` attribute can be used
|
||||||
the test has been properly updated.
|
until the test has been properly updated.
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
.. versionchanged:: 1.5
|
||||||
|
|
||||||
|
@ -964,55 +965,6 @@ to test the effects of commit and rollback:
|
||||||
|
|
||||||
``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`.
|
``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`.
|
||||||
|
|
||||||
.. attribute:: TransactionTestCase.reset_sequences
|
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
|
|
||||||
Setting ``reset_sequences = True`` on a ``TransactionTestCase`` will make
|
|
||||||
sure sequences are always reset before the test run::
|
|
||||||
|
|
||||||
class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase):
|
|
||||||
reset_sequences = True
|
|
||||||
|
|
||||||
def test_animal_pk(self):
|
|
||||||
lion = Animal.objects.create(name="lion", sound="roar")
|
|
||||||
# lion.pk is guaranteed to always be 1
|
|
||||||
self.assertEqual(lion.pk, 1)
|
|
||||||
|
|
||||||
Unless you are explicitly testing primary keys sequence numbers, it is
|
|
||||||
recommended that you do not hard code primary key values in tests.
|
|
||||||
|
|
||||||
Using ``reset_sequences = True`` will slow down the test, since the primary
|
|
||||||
key reset is an relatively expensive database operation.
|
|
||||||
|
|
||||||
.. attribute:: TransactionTestCase.available_apps
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This attribute is a private API. It may be changed or removed without
|
|
||||||
a deprecation period in the future, for instance to accomodate changes
|
|
||||||
in application loading.
|
|
||||||
|
|
||||||
It's used to optimize Django's own test suite, which contains hundreds
|
|
||||||
of models but no relations between models in different applications.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
|
|
||||||
By default, ``available_apps`` is set to ``None`` and has no effect.
|
|
||||||
Setting it to a list of applications tells Django to behave as if only the
|
|
||||||
models from these applications were available:
|
|
||||||
|
|
||||||
- Before each test, Django creates content types and permissions only for
|
|
||||||
these models.
|
|
||||||
- After each test, Django flushes only the corresponding tables. However,
|
|
||||||
at the database level, truncation may cascade to other related models,
|
|
||||||
even if they aren't in ``available_apps``.
|
|
||||||
|
|
||||||
Since the database isn't fully flushed, if a test creates instances of
|
|
||||||
models not included in ``available_apps``, they will leak and they may
|
|
||||||
cause unrelated tests to fail. Be careful with tests that use sessions;
|
|
||||||
the default session engine stores them in the database.
|
|
||||||
|
|
||||||
TestCase
|
TestCase
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue