skip to content

What does Django's test --keepdb option save, and when can a kept test database give you misleading results?

level: middleimportance: should knowfreq 40%

answer

  1. skips create and destroy
  2. unapplied migrations still run
  3. edited migrations are not replayed
  4. branch switching

basics

~20 s

--keepdb skips creating and destroying the test database, only applying unapplied migrations. It misleads when an already-applied migration was edited, when switching branches leaves a different schema, or when MIGRATE is False and models changed.

solid answer

~50 s

`manage.py test --keepdb` reuses the existing test database instead of creating it and running every migration from scratch, then leaves it in place afterwards; if the database does not exist it is created on the first run. On each run `migrate` still applies any **unapplied** migrations, so new migrations are picked up. What it cannot see is a migration that is already recorded as applied but has since been edited, a branch switch that leaves tables from another line of migrations, or model changes when `TEST["MIGRATE"]` is `False`, where missing tables are created but existing ones are never altered. Tests then pass or fail against a schema production will never have. With `--parallel`, the clones are kept too. The cure is one run without `--keepdb` (with `--noinput` so the stale database is dropped), and CI should normally build fresh.

code

bash · 2 lines
bash
$ python manage.py test --keepdb
Using existing test database for alias 'default'...

go deeper

for a junior

Know that --keepdb reuses the test database between runs to skip creation and migration from scratch.

for a middle

Explain that migrate still applies unapplied migrations, that the name in django_migrations is what counts, and why data does not accumulate normally.

for a senior

Recognise stale-schema failures from edited migrations, branch switches and MIGRATE False, and know the fresh-run cure and the CI policy.

for a principal

Balance local speed against trust in results: when a cached schema is acceptable and how CI proves migrations from zero.

## What `--keepdb` skips Creating the test database is often the slowest fixed cost in a Django test run: `CREATE DATABASE`, then every migration from `0001` onward, then `createcachetable`. On a project with hundreds of migrations that can take a minute or more before the first test runs. `manage.py test --keepdb` changes two steps: - **At start-up** the runner uses the existing test database (the log says "Using existing test database for alias 'default'"). If it does not exist yet, it is created normally. - **At the end** the database is **not destroyed**, so the next run can reuse it. `migrate` still runs every time, so any migration not yet recorded in the test database's `django_migrations` table is applied. With `--parallel`, the per-process clones are kept as well ("Using existing clone"). ## Why data does not usually pile up A kept database is only reused structurally. Per-test cleanliness comes from the test case classes: `TestCase` wraps each test in a transaction that is rolled back, and `TransactionTestCase` flushes tables after each test. So a normal run leaves the kept database as empty as migrations left it. The exception is a run killed in the middle of a `TransactionTestCase`, whose cleanup happens after the test and may never run; rows can then survive into the next kept run. ## When a kept database lies `migrate` decides what to do by the names recorded in `django_migrations`, not by the contents of the migration files. That makes several everyday situations dangerous: 1. **Editing an applied migration.** You change `0042_add_discount` locally after it ran once. The kept database already records `0042` as applied, so the edited operations never run, and tests exercise the old schema. 2. **Switching branches.** Branch A adds `0043_a`, branch B adds `0043_b`. After testing on A with `--keepdb`, B's run applies `0043_b` on top of a database that also contains A's changes. Tests may pass on a schema no real deployment has. 3. **`TEST["MIGRATE"] = False`.** Tables are built from the models instead of migrations. With `--keepdb`, a table that already exists is not rebuilt, so a model field added since the last fresh run is simply missing. 4. **Squashing or deleting migrations.** Recorded names no longer match files, and the kept database's history diverges from what `migrate` would build from scratch. | Situation | Picked up with `--keepdb`? | |---|---| | New migration file added | yes, applied as unapplied | | Existing migration edited | no | | Branch switch to divergent migrations | partially, with leftovers | | Model change with `MIGRATE: False` | no, existing tables untouched | ## Using it safely - Use `--keepdb` for the **local edit-test loop**, where it pays off many times an hour. - When in doubt, run **once without it**. Because the old test database still exists, the runner asks whether to delete it; `--noinput` answers yes, destroys it and builds a fresh one. - Let **CI build fresh** by default. If CI caches a test database for speed, key the cache on the migration files so any change invalidates it. - Remember that `--keepdb` saves setup time only. If most of a slow suite's time is in the tests themselves, look at `--parallel`, `setUpTestData` and the password hasher instead. ```bash # fast local loop python manage.py test orders --keepdb # after editing a migration or switching branches python manage.py test orders --noinput ``` ## How the reuse decision is made It helps to know the exact rule, because it explains every failure above. On each run with `--keepdb`: 1. The runner tries to create the test database and treats "already exists" as success. 2. It calls `migrate`, which reads `django_migrations` in that database and runs only the migrations whose names are missing. 3. It does not compare schemas, checksums or file timestamps. Nothing looks at what a recorded migration now contains. Everything `--keepdb` gets wrong follows from step 3: the kept database is trusted as long as its recorded history is a subset of the migrations on disk.

  • After running tests with --keepdb, what happens when you run once without it?
    The runner tries to create `test_<name>`, finds it already exists and asks whether to delete it. Answer yes, or pass `--noinput`, and it drops and recreates the database from all migrations; at the end it destroys it again. That one fresh run is the standard way to clear a stale kept schema.

saying these in an interview costs you the question

  • Believing --keepdb skips migrations entirely on later runs
  • Thinking --keepdb re-runs a migration whose file was edited
  • Saying --keepdb keeps the data each test created between runs
  • Assuming --keepdb makes the individual tests run faster
  • Recommending --keepdb on CI without any cache invalidation