Speed up your test suite

Create shared test data in setUpTestData instead of setUp

Django runs setUp before every test, so its rows are inserted again each time. setUpTestData creates them once per TestCase class, and every test still starts clean.

setUp() runs before every test method. When it creates database rows (a user, an organization, a few projects with their tasks) a class with 30 tests creates them 30 times, and throws them away 30 times. Often most of a test's time goes into setup that's identical for every test in the class.

Django's TestCase has a class-level hook for exactly this: setUpTestData() runs once per class, and every test still sees the data exactly as it was created.

Why setUp is slow

Everything in setUp is repeated per test: each baker.make() or Model.objects.create() is an INSERT (often several, with related objects), each create_user(password=...) hashes a password, and each factory may run signals and post-generation hooks. The work is small for one test and large for a suite. A setUp that takes 50 ms in a class of 40 tests costs 2 seconds, and a project has many such classes.

The fix

Move objects that tests only read into setUpTestData, a classmethod that assigns to cls:

# Before: runs before each of the class's tests
from django.test import TestCase
from model_bakery import baker

from projects.models import Organization, Project


class ProjectListTests(TestCase):
    def setUp(self):
        self.org = baker.make(Organization)
        self.user = baker.make("auth.User")
        self.org.members.add(self.user)
        self.projects = baker.make(Project, organization=self.org, _quantity=20)
        self.client.force_login(self.user)
# After: data is created once for the class
from django.test import TestCase
from django.urls import reverse
from model_bakery import baker

from projects.models import Organization, Project


class ProjectListTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.org = baker.make(Organization)
        cls.user = baker.make("auth.User")
        cls.org.members.add(cls.user)
        cls.projects = baker.make(Project, organization=cls.org, _quantity=20)

    def setUp(self):
        self.client.force_login(self.user)

    def test_lists_projects(self):
        response = self.client.get(reverse("project-list", args=[self.org.slug]))
        self.assertEqual(len(response.context["projects"]), 20)

Tests keep using self.org and self.user as before. force_login stays in setUp: the test client is created fresh for every test, so its session has to be set up per test, and it doesn't touch the slow parts (see fast password hashing for why force_login beats login).

Why each test still starts clean

TestCase wraps the whole class in a transaction, runs setUpTestData inside it, and wraps each test in a savepoint. When a test ends, Django rolls back to the savepoint:

  • rows a test inserts, updates or deletes are back to how setUpTestData left them for the next test
  • since Django 3.2, the objects assigned to cls are deep-copied for each test the first time it reads them, so self.org.name = "changed" in one test doesn't leak into the next

You don't have to refresh_from_db() between tests. The deep copy has a cost of its own when the attributes are large: setUpTestData deep copies explains what to keep out of them.

What stays in setUp

setUpTestData is for database rows. Keep per-test work in setUp:

  • logging in with the test client (force_login)
  • unittest.mock patches that must start and stop per test
  • temporary files and directories
  • anything outside the database that the test changes: cache entries, files in MEDIA_ROOT, a search index. The rollback doesn't undo those, so creating them once per class would let tests see each other's changes.

With pytest-style tests

pytest-django's db fixture is function-scoped, so a class- or module-scoped pytest fixture can't use the database the same way. You have two options:

  • Keep TestCase classes for the tests that share expensive data. pytest runs them as usual, setUpTestData included.
  • For read-only data every test needs (countries, plans, permissions), load it once for the whole session by extending django_db_setup; see loading fixtures once.

To make the remaining per-test data cheaper, create it in bulk: faster test factories.

Keep the tests correct

  • Attributes assigned in setUpTestData are deep-copied per test, so in-memory changes don't leak between tests. Objects that can't be deep-copied (locks, open files, some clients) raise a TypeError when a test reads them; create those in setUp or at module level.
  • Changes a test saves to the database are rolled back, but only inside TestCase. TransactionTestCase has no setUpTestData, and on a database without transactions (MySQL with MyISAM) Django calls setUpTestData before every test, so there is no speed-up.
  • Move one class at a time and run the suite in random order (with pytest-randomly installed, or manage.py test --shuffle) to catch tests that depended on running after another test.

How flakytest finds it

flakytest measures the time spent in setUp methods in your test files. When it's at least half a second and 2% of test time, in classes with more than one test, it suggests Create shared test data in setUpTestData instead of setUp. The summary breaks setUp's time down by category (for example database and password hashing) and names the classes with the most to gain, with their test counts. Run flakytest run again after moving the data and flakytest compare shows which tests got faster and that none of them changed outcome.

Further reading

Related guides

  • setUpTestData deep copies and expensive copy.deepcopy: Django deep-copies attributes set in setUpTestData for every test that reads them. Big lists, prefetched instances and querysets make it slow. What to store instead.
  • Faster factory_boy and model_bakery factories: Slow factory_boy and model_bakery factories? Create shared data once, use build() or baker.prepare() instead of saving, bulk create rows, reuse one Faker instance and tune Hypothesis.
  • Use TestCase instead of TransactionTestCase: TransactionTestCase and transaction=True empty every table after each test. Django's TestCase rolls back a transaction instead. When to use which, and how to switch.
  • Django testing best practices: How to write fast, reliable Django tests: TestCase vs TransactionTestCase, setUpTestData, model_bakery, force_login, test settings, assertNumQueries and parallel runs.

Updated October 18, 2026 · Suggest a change