How to convert a TestCase from setUp() to setUpTestData()2021-04-12
TestCase class provides the
setUpTestData() hook for creating your test data.
It is faster than using the
setUp() hook because it creates the test data only once per test case, rather than per test.
setUpTestData() is one of the most reliable ways to speed up a test suite, with no costs beyond from the time it might take to change your code.
I’ve often found it confers a 3x speedup, or more.
Here’s my how-to guide for doing this conversion for a single
Rinse and repeat across your code base!
Example Test Case
We’ll convert this test case:
0. Install django-testdata on Django < 3.2
Before Django 3.2, use of
setUpTestData() has a major caveat: Django rolls back changes to model instances in the database but not in memory.
This means you often need extra code to re-fetch data to ensure tests are correctly isolated.
Django 3.2 includes a fix:
TestCase now copies any objects created in
setUpTestData() on-demand for subsequent tests.
Your tests can thus change objects as needed, with following tests seeing the original versions.
Luckily, this behaviour is available for older Django versions in the
Install this if you’re using Django < 3.2 - you can remove it when you upgrade in the future.
1. Run the target test case
This will check the tests currently pass and gives us a baseline for how long they take. Run the test command to run just this test case, with the database reuse flag to reduce startup time.
For example with Django’s test framework:
Or with pytest:
2. Add a stub
setUpTestData() class method in the test case class.
On Django 3.2+ this can be a vanilla class method:
On Django < 3.2 you’ll also need to import and use
@wrap_testdata from django-testdata:
3. Move data creation from
Move all code that creates data from
setUpTestData(), and change its use of
This can mean moving code that creates model instances, supports such creation, or that creates other expensive objects.
Other per-test setup, such as using methods on the test client, cannot normally live in
setUpTestData() as it won’t be reset between tests.
If nothing remains in
setUp() after this, delete it.
In our example this change looks like:
With this final result:
4. Re-run tests
Re-run the test command from step one to check the conversion has been successful, and to see any time savings:
We don’t see much difference in this example because there isn’t much test data, and we only have two tests, but it is a few milliseconds faster. In real world test cases you should see more change.
That’s all it takes! Repeat for the next test case in your suite.
If you’re trying to convert your whole test suite, try working in batches, starting with the slowest cases.
Thanks to Simon Charette for creating django-testdata and submitting it for merging into Django 3.2.
May your tests run ever faster,
Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.
One summary email a week, no spam, I pinky promise.
- New Testing Features in Django 3.2
- How to Unit Test a Django Form
- Announcing a Regional Discount for “Speed Up Your Django Tests”
Tags: django, python
© 2021 All rights reserved.