소스 검색

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.
Aymeric Augustin 11 년 전
부모
커밋
55cbd65985
4개의 변경된 파일130개의 추가작업 그리고 87개의 파일을 삭제
  1. 17 11
      django/core/management/commands/flush.py
  2. 23 12
      django/test/testcases.py
  3. 74 0
      docs/topics/testing/advanced.txt
  4. 16 64
      docs/topics/testing/overview.txt

+ 17 - 11
django/core/management/commands/flush.py

@@ -32,9 +32,10 @@ class Command(NoArgsCommand):
         connection = connections[db]
         verbosity = int(options.get('verbosity'))
         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)
         allow_cascade = options.get('allow_cascade', False)
+        inhibit_post_syncdb = options.get('inhibit_post_syncdb', False)
 
         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"
                     "The full error: %s") % (connection.settings_dict['NAME'], e)
                 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
-            # 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(db, m)
-                ])
-            emit_post_sync_signal(set(all_models), verbosity, interactive, db)
+
+            if not inhibit_post_syncdb:
+                self.emit_post_syncdb(verbosity, interactive, db)
 
             # Reinstall the initial_data fixture.
             if options.get('load_initial_data'):
@@ -93,3 +87,15 @@ Are you sure you want to do this?
 
         else:
             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)

+ 23 - 12
django/test/testcases.py

@@ -24,6 +24,7 @@ from django.core.exceptions import ValidationError, ImproperlyConfigured
 from django.core.handlers.wsgi import WSGIHandler
 from django.core.management import call_command
 from django.core.management.color import no_style
+from django.core.management.commands import flush
 from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
     WSGIServerException)
 from django.core.urlresolvers import clear_url_caches, set_urlconf
@@ -196,9 +197,9 @@ class SimpleTestCase(ut2.TestCase):
     def _pre_setup(self):
         """Performs any pre-test setup. This includes:
 
-           * If the Test Case class has a 'urls' member, replace the
-             ROOT_URLCONF with it.
-           * Clearing the mail test outbox.
+        * Creating a test client.
+        * If the class has a 'urls' attribute, replace ROOT_URLCONF with it.
+        * Clearing the mail test outbox.
         """
         self.client = self.client_class()
         self._urlconf_setup()
@@ -212,6 +213,10 @@ class SimpleTestCase(ut2.TestCase):
             clear_url_caches()
 
     def _post_teardown(self):
+        """Performs any post-test things. This includes:
+
+        * Putting back the original ROOT_URLCONF if it was changed.
+        """
         self._urlconf_teardown()
 
     def _urlconf_teardown(self):
@@ -732,13 +737,17 @@ class TransactionTestCase(SimpleTestCase):
     def _pre_setup(self):
         """Performs any pre-test setup. This includes:
 
-           * Flushing the database.
-           * If the Test Case class has a 'fixtures' member, installing the
-             named fixtures.
+        * If the class has an 'available_apps' attribute, restricting the app
+          cache to these applications, then firing post_syncdb -- it must run
+          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()
         if self.available_apps is not None:
             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:
             self._fixture_setup()
         except Exception:
@@ -782,9 +791,9 @@ class TransactionTestCase(SimpleTestCase):
     def _post_teardown(self):
         """Performs any post-test things. This includes:
 
-           * Putting back the original ROOT_URLCONF if it was changed.
-           * Force closing the connection, so that the next test gets
-             a clean cursor.
+        * Flushing the contents of the database, to leave a clean slate. If
+          the class has an 'available_apps' attribute, post_syncdb isn't fired.
+        * Force-closing the connection, so the next test gets a clean cursor.
         """
         try:
             self._fixture_teardown()
@@ -801,12 +810,14 @@ class TransactionTestCase(SimpleTestCase):
             cache.unset_available_apps()
 
     def _fixture_teardown(self):
-        # Allow TRUNCATE ... CASCADE when flushing only a subset of the apps
-        allow_cascade = self.available_apps is not None
+        # Allow TRUNCATE ... CASCADE and don't emit the post_syncdb signal
+        # when flushing only a subset of the apps
         for db_name in self._databases_names(include_mirrors=False):
             call_command('flush', verbosity=0, interactive=False,
                          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):
         items = six.moves.map(transform, qs)

+ 74 - 0
docs/topics/testing/advanced.txt

@@ -155,6 +155,80 @@ If there are any circular dependencies in the
 :setting:`TEST_DEPENDENCIES` definition, an ``ImproperlyConfigured``
 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
 =====================================
 

+ 16 - 64
docs/topics/testing/overview.txt

@@ -213,6 +213,8 @@ advanced settings.
 
     The :ref:`advanced multi-db testing topics <topics-testing-advanced-multidb>`.
 
+.. _order-of-tests:
+
 Order in which tests are executed
 ---------------------------------
 
@@ -908,8 +910,8 @@ TransactionTestCase
 
 .. class:: TransactionTestCase()
 
-Django ``TestCase`` classes make use of database transaction facilities, if
-available, to speed up the process of resetting the database to a known state
+Django's ``TestCase`` class (described below) makes use of database transaction
+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
 effects of transaction commit and rollback cannot be tested by a Django
 ``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
   back at the end of the test. Both explicit commits like
   ``transaction.commit()`` and implicit ones that may be caused by
-  ``Model.save()`` are replaced with a ``nop`` operation. This guarantees that
-  the rollback at the end of the test restores the database to its initial
-  state.
+  ``transaction.atomic()`` are replaced with a ``nop`` operation. This
+  guarantees that the rollback at the end of the test restores the database to
+  its initial state.
 
   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
@@ -940,22 +942,21 @@ to test the effects of commit and rollback:
     While ``commit`` and ``rollback`` operations still *appear* to work when
     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
-    use ``TransactionalTestCase`` when testing transactional behavior.
-
-.. note::
+    use ``TransactionTestCase`` when testing transactional behavior.
 
-    .. versionchanged:: 1.5
+.. versionchanged:: 1.5
 
-    Prior to 1.5, ``TransactionTestCase`` flushed the database tables *before*
-    each test. In Django 1.5, this is instead done *after* the test has been run.
+    Prior to 1.5, :class:`~django.test.TransactionTestCase` flushed the
+    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
     key values started at one in :class:`~django.test.TransactionTestCase`
     tests.
 
-    Tests should not depend on this behavior, but for legacy tests that do, the
-    :attr:`~TransactionTestCase.reset_sequences` attribute can be used until
-    the test has been properly updated.
+    Tests should not depend on this behavior, but for legacy tests that do,
+    the :attr:`~TransactionTestCase.reset_sequences` attribute can be used
+    until the test has been properly updated.
 
 .. versionchanged:: 1.5
 
@@ -964,55 +965,6 @@ to test the effects of commit and rollback:
 
 ``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
 ~~~~~~~~
 
@@ -1495,7 +1447,7 @@ Emptying the test outbox
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
 If you use any of Django's custom ``TestCase`` classes, the test runner will
-clear thecontents of the test email outbox at the start of each test case.
+clear the contents of the test email outbox at the start of each test case.
 
 For more detail on email services during tests, see `Email services`_ below.