Chapter 22 · The test framework
22.4 Which databases a test uses: databases
SimpleTestCase names the aliases of DATABASES a test case class may use. The runner reads it to choose which test databases to make (DiscoverRunner), SimpleTestCase reads it to make every other alias raise DatabaseOperationForbidden, and skipUnlessDBFeature and its relatives read it before they ask the default database what it can do.
Django’s documentation gives the reason a SimpleTestCase names no database by default: its tests are not run in a transaction, so a write would affect later tests.
The attribute
SimpleTestCase is an empty set and TransactionTestCase is {"default"}, which TestCase inherits; a class may name other aliases, or give "__all__" for every alias. The same aliases, mirrors apart, are where a TransactionTestCase loads fixtures and flushes, and all of them where a TestCase opens its atomic blocks (TransactionTestCase; Emptying the tables after each test: TransactionTestCase, A transaction around each test: TestCase).
SimpleTestCase checks the attribute as the class is set up. "__all__" becomes a frozenset of every alias; an alias that django does not know raises ImproperlyConfigured, naming the class and the alias, and the closest alias where difflib finds one. The frozenset it returns replaces the class’s attribute.
What the runner makes
DiscoverRunner reads databases from every test of the suite, "__all__" standing for every alias; a plain unittest has none. The result maps each alias to whether any test that uses it sets TransactionTestCase. DiscoverRunner logs the aliases no test uses at the debug level, which a runner made without a logger, as the test command makes it, prints from verbosity 2.
DiscoverRunner passes the aliases, and those to serialize, to DiscoverRunner, and the aliases to DiscoverRunner, so that the database checks run for these alone, and keeps both sets on the suite for a ParallelTestSuite’s workers.
A module where one class names the archive and two use a database they do not name:
# harbour/office/tests/test_archive.py
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from harbour.fleet.models import Ship
class ShipCountTests(SimpleTestCase):
def test_count(self):
Ship.objects.count()
class ArchiveCountTests(TestCase):
def test_archive_count(self):
Ship.objects.using("archive").count()
class ArchiveTests(TestCase):
databases = {"default", "archive"}
@skipUnlessDBFeature("has_select_for_update")
def test_lock_ships(self):
list(Ship.objects.select_for_update())
Both test databases are made, since a class names "archive"; migrate’s lines, the tracebacks and the end are left out:
$ python manage.py test harbour.office.tests.test_archive -v 2
Found 3 test(s).
Creating test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
...
Creating test database for alias 'archive' ('file:memorydb_archive?mode=memory&cache=shared')...
...
System check identified no issues (0 silenced).
test_archive_count (harbour.office.tests.test_archive.ArchiveCountTests.test_archive_count) ... FAIL
test_lock_ships (harbour.office.tests.test_archive.ArchiveTests.test_lock_ships) ... skipped "Database doesn't support feature(s): has_select_for_update"
test_count (harbour.office.tests.test_archive.ShipCountTests.test_count) ... FAIL
...
django.test.testcases.DatabaseOperationForbidden: Database queries to 'archive' are not allowed in this test. Add 'archive' to harbour.office.tests.test_archive.ArchiveCountTests.databases to ensure proper test isolation and silence this failure.
...
django.test.testcases.DatabaseOperationForbidden: Database queries to 'default' are not allowed in SimpleTestCase subclasses. Either subclass TestCase or TransactionTestCase to ensure proper test isolation or add 'default' to harbour.office.tests.test_archive.ShipCountTests.databases to silence this failure.
...
Making and destroying them: setup_databases
DiscoverRunner hands the aliases to django, with the verbosity, --noinput, --keepdb, --debug-sql and --parallel. There get_unique_databases_and_mirrors groups the aliases asked for by the database each reaches, "default" first in its group, and takes every alias whose "TEST" names a "MIRROR", asked for or not (Test databases: BaseDatabaseCreation tells both). It orders the groups: an alias waits for those its "TEST" lists under "DEPENDENCIES", and without that key an alias other than "default" whose database differs from "default"'s waits for "default". dependency_ordered takes, round after round, the groups whose dependencies are met, and raises ImproperlyConfigured where an alias depends on one of its own group or a round takes none.
For each group the creation of the first alias makes the test database, and its clones for --parallel; the group’s other aliases are pointed at it, and each mirror at the database of the alias it names; once all are made, those to serialize are serialized. Last, --debug-sql sets force_debug_cursor on every alias’s connection, used or not, so that each statement is logged whatever settings.DEBUG is (Cursors: executing a statement, the query log and execute wrappers).
It returns, for each alias asked for that is not a mirror, the connection, the name the settings gave the database, and whether this alias made it. In its finally, run_tests gives that list to teardown_databases, which destroys each made database’s clones and then the database, or leaves them with --keepdb, and puts back the "NAME" of the alias that made it.
Through the test command, a misspelt alias never reaches _validate_databases: get_unique_databases_and_mirrors passes over it, the check command fails on it once the others are made, and the finally destroys them. For {"default", "archiv"} the run ended with ConnectionDoesNotExist: “The connection ‘archiv’ doesn’t exist.”
The guard: _add_databases_failures
SimpleTestCase, after the class’s settings overrides, calls SimpleTestCase. It validates databases, then, on the connection of each alias the class does not name, replaces the methods BaseDatabaseFeatures lists: "connect" and "temporary_connection", “connections” in the message, and "cursor" and "chunked_cursor", “queries”. Each becomes a _DatabaseFailure, which keeps the method and, called, raises DatabaseOperationForbidden with the class’s message: SimpleTestCase, or TransactionTestCase’s own. The exception is an AssertionError, so a refused query in a test method is reported as a failure; one in TestCase is an error of setUpClass.
A thread has connection objects of its own (Connections by alias: DATABASES and ConnectionHandler), and those methods are replaced only on the thread that set the class up. For the rest, the class replaces BaseDatabaseWrapper with what SimpleTestCase makes, which refuses to open a connection to an alias the class does not name, as “threaded connections”: another thread’s, and in the test’s own thread one opened other than through the replaced methods, as an atomic block opens it through BaseDatabaseWrapper. It lets through a connection already open, the connection without a database (NO_DB_ALIAS), and, a comment says, one made by hand under an alias DATABASES lacks.
Both are undone as class cleanups after tearDownClass: SimpleTestCase puts each method back, and ensure_connection is restored.
Skipping by feature: skipUnlessDBFeature
skipIfDBFeature skips where the database has any of the features named, skipUnlessDBFeature where it lacks any, skipUnlessAnyDBFeature where it has none. A feature is named as a string and read from the features of django, an instance of its backend’s subclass of BaseDatabaseFeatures, so the database asked is always "default"'s (What a database can do: BaseDatabaseFeatures). Django’s documentation says skipIfDBFeature needs all the features; the code needs one. All three are made by _deferredSkip.
On a method, the wrapper acts as the test runs: ValueError if the test’s databases lacks "default", then unittest if the condition holds. The skip is raised inside the test method, so SimpleTestCase, setUp, tearDown and SimpleTestCase all run around it; a method marked by unittest.skip runs none of them (One test, in order: SimpleTestCase).
On a class, it sets "__unittest_skip__" to a CheckCondition. The descriptor asks its conditions only when the attribute is read, as unittest comes to set the class up, after the test databases are made: importing the module asks the database nothing. A skipped base skips the class; otherwise the first condition that holds sets the attribute to True, with the reason. Where the class does not name "default", the condition raises ValueError instead, deferred, a comment says, so that the module can be imported; it is raised outside any test, and ends the run.