Chapter 22 · The test framework
22.7 A transaction around each test: TestCase
TestCase undoes what each test writes by rolling it back, where its databases have transactions and savepoints: TestCase opens an atomic block on each database the class uses and calls TestCase inside it, TestCase opens a savepoint for each test, and TestCase rolls back to it. TestData gives each test copies of what setUpTestData kept on the class.
TransactionTestCase undoes what a test wrote by emptying the tables after each test (Emptying the tables after each test: TransactionTestCase). TestCase lets the database forget instead: the class’s rows are written inside a transaction that is never committed, and each test runs inside a savepoint of it, rolled back as the test ends.
TestCase replaces setUpClass and tearDownClass, and _fixture_setup and _fixture_teardown, which TransactionTestCase and TransactionTestCase call around each test (One test, in order: SimpleTestCase). Each turns on whether every alias in the class’s databases has BaseDatabaseFeatures, and all but _fixture_teardown on whether each has BaseDatabaseFeatures too, as TestCase and TestCase ask. SQLite, PostgreSQL and Oracle have both; MySQL too, unless its engine is MyISAM.
What ShipPageTests sends
ShipPageTests from The test framework, run with --debug-sql and a handler printing each statement logged to "django, sends:
"default", each beside the hook or the test that sent it. The class’s transaction is begun and rolled back once; inside it each test’s savepoint is made, rolled back to and released.The class’s block sent "BEGIN" on entering, before any write: SQLite’s backend begins a transaction with that statement, followed by the "transaction_mode" of its "OPTIONS" where one is set. A savepoint is named from the thread’s number and the connection’s count of savepoints (Transactions: autocommit, atomic and savepoints).
The class’s block: TestCase.setUpClass
TestCase first calls the parent’s: SimpleTestCase enters the class’s overridden settings and guards the aliases it does not name, and TransactionTestCase runs no _pre_setup for a TestCase. Without both features it stops there. Otherwise, in order:
TestCasemakes an._enter_atomics atomicblock for each alias of the class, mirrors included, setsAtomicon it, which lets a durable block directly inside it through (Transactions: autocommit,._from_testcase atomicand savepoints), and enters it, beginning a transaction.- The class’s
fixturesare loaded by loaddata into each database that is not a mirror (Fixtures: dumpdata and loaddata). - The class’s own attributes are noted as they stand, and
setUpTestDatais called. - Each attribute whose value is not the object it was before, a new one or one rebound, is replaced on the class by a
TestDataholding the value.
If the fixtures or setUpTestData raise, TestCase rolls the blocks back first. unittest calls no tearDownClass after a setUpClass that raised.
A copy for each test: TestData
TestData is a descriptor with a __get__ and no __set__. Read from the class, it returns the value itself; read from a test, it copies the object with copy.deepcopy and sets the copy on the test under the same name, where later reads find it first. unittest makes an instance of the class for each test method, so each test copies afresh. A test’s copies share one memo, kept on the test as "_testdata_memo" (TestData), so that, the docstring says, they keep their relations.
# harbour/office/tests/test_copies.py
from django.test import TestCase
from harbour.fleet.models import Port, Ship
class ShipCopyTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.valletta = Port.objects.create(name="Valletta", country="Malta")
cls.endeavour = Ship.objects.create(name="Endeavour", tonnage=366, home=cls.valletta)
def test_a_rename(self):
self.endeavour.name = "Resolution"
print(self.endeavour.name, self.endeavour.home is self.valletta)
def test_b_read(self):
print(self.endeavour.name, ShipCopyTests.endeavour.name)
print(ShipCopyTests.__dict__["endeavour"])
$ python manage.py test harbour.office.tests.test_copies
...
Resolution True
.Endeavour Endeavour
<TestData: name='endeavour', data=<Ship: Endeavour>>
.
...
OK
A test’s copies keep their relations, and a rename in one test is not seen by the next.
Each test’s block: TestCase._fixture_setup
TestCase, where the databases have transactions, refuses reset_sequences with a TypeError and calls _enter_atomics again. Inside the class’s blocks, Atomic.__enter__ makes each new block a savepoint (TestCase; Transactions: autocommit, atomic and savepoints).
Where setUpClass stopped early, each test gets what it skipped. With transactions but no savepoints, each test’s block begins a transaction, and the fixtures and setUpTestData are loaded inside it. Without transactions, setUpTestData is called and then TransactionTestCase, and the test ends with the parent’s flush. Nothing is wrapped in TestData; Django’s documentation notes the speed lost without transactions.
The end of a test: TestCase._fixture_teardown
TestCase, where the databases have transactions, goes through the class’s aliases in reverse order. It calls BaseDatabaseWrapper on each connection that TestCase passes: one whose features have BaseDatabaseFeatures, that BaseDatabaseWrapper says works, and that is not marked by BaseDatabaseWrapper. Then, in a finally, _rollback_atomics calls django with True and leaves each block, which rolls back to its savepoint and releases it, or, where the block began the transaction, rolls that back (Transactions: autocommit, atomic and savepoints).
SQLite, PostgreSQL and Oracle make foreign keys deferred, checked at the commit (The connection object: BaseDatabaseWrapper and what a backend provides), and a TestCase’s transaction never commits. The release notes of Django 1.10, which added the check, give its purpose as catching bugs.
# harbour/office/tests/test_constraints.py
from django.test import TestCase
from harbour.fleet.models import Ship
class OrphanShipTests(TestCase):
def test_ship_of_no_port(self):
Ship.objects.create(name="Beagle", tonnage=235, home_id=999)
$ python manage.py test harbour.office.tests.test_constraints
...
.E
...
django.db.utils.IntegrityError: The row in table 'fleet_ship' with primary key '1' has an invalid foreign key: fleet_ship.home_id contains a value '999' that does not have a corresponding value in fleet_port.id.
...
Ran 1 test in 0.001s
The deferred key let Beagle in with a home port that does not exist, and unittest recorded a success, the dot. Then "PRAGMA foreign_key_check" found the row, SQLite’s check_constraints raised, and SimpleTestCase added an error to the same test, the E.
The end of the class: TestCase.tearDownClass
TestCase, where the databases have both features, rolls the class’s blocks back, ending each transaction, and closes every connection that was opened; in every case it then calls the parent’s. Between tests TransactionTestCase closes none, as TestCase answers false wherever there are transactions.
What a test inside the blocks does not see
Inside a TestCase’s blocks nothing is committed. A callback registered with on_commit does not run; TestCase collects such callbacks and can call them (Work held until the commit: on_commit). Another connection does not see the rows, which the docstring of LiveServerTestCase gives as the reason it is a TransactionTestCase (A server in a thread: LiveServerTestCase).