Slow tests not only waste developers time on waiting but also make it difficult to follow TDD best practices (such as red-green testing). If it needs minutes or even longer to run test suit, it leads to infrequent whole suit run. Which in its turn leads to late bugs discovery and fix.
In this post, I'll tell how to speed up tests of your Django application. Also, I'll describe what kills your tests performance. I will use simple tests suit as an example in this post. You can find it on GitHub.
Parallel testing
The most simple way to speed up tour tests without the need to make any code changes - run tests in parallel.
Django provides --parallel
option for running tests in parallel. This parameter also accepts an optional number of
processes. If this number wasn't provided it uses processes count equal to count of processor cores. For most of the
cases, this is optimal.
Sequential running of tests from the example on my machine:
# python manage.py test
...........
----------------------------------------------------------------------
Ran 11 tests in 8.012s
Let's try to use --parallel
option:
# python manage.py test --parallel
...........
----------------------------------------------------------------------
Ran 11 tests in 2.628s
As you can see tests completed more than 3 times faster.
It's worth noting that Django distributes execution of different test cases between different processes. Consequently, if you have fewer test cases then processor cores Django will decrease the count of processes to match count of test cases. In our example, we have only 3 test cases, so parallelism is limited to 3 processes. On real projects you probably have hundreds or even thousands of test cases and this problem won't touch you.
In some cases Django can't collect tracebacks on tests failures in parallel mode. In that case, you need to re-run tests sequentially.
Use weak passwords hashing algorithm
By default, Django uses a computationally difficult algorithm for passwords hashing. Regularly in new Django versions, the hashing algorithm is reinforced. It needs for security, so intruder will need a lot of computing power to break passwords.
We don't need such a strong algorithm in tests. We can go with something faster, such as MD5.
Let's add switching to MD5 for tests to settings.py
:
import sys
TESTING = 'test' in sys.argv
if TESTING:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
And run tests again:
# python manage.py test --parallel
...........
----------------------------------------------------------------------
Ran 11 tests in 0.564s
4.65x times faster 🚀 I saw how this simple hack accelerated huge test suit by an order of magnitude.
Create data only when we need it
A frequent mistake that slows down tests execution is to have a base test case with huge setUp
method that creates data
for the whole test suit. At first sight, it may seem convenient, but it kills your tests performance because
all data are created before each test no matter if you need them in this particular test or not.
To fix this problem you need to simplify setUp
method. Ideally fully remove setUp
method from the base test case.
Test data should be created in particular test cases only when they are needed.
I added corresponding changes to our example. Let's see how this will affect tests running time:
# python manage.py test --parallel
...........
----------------------------------------------------------------------
Ran 11 tests in 0.353s
That's 60% more speed up.
setUpTestData
Base Django test case allows creating test data on the level of the test case instead of the test method. This allows
vastly accelerate tests execution. You need to move data creation to class method setUpTestData
.
Objects created in
setUpTestData
shouldn't change while test running because it can lead to instability due to not fully isolated tests.
I added changes to the example here. Let's run tests again:
# python manage.py test --parallel
...........
----------------------------------------------------------------------
Ran 11 tests in 0.349s
Seems that we don't get the significant speed up. Let's see what's happen if we add
some more tests.
Without setUpTestData
I get 0.536s
duration and with setUpTestData
- 0.348s
. As you can see with setUpTestData
duration doesn't grow on adding new tests (besides duration of running the test itself) because test data aren't created
before each test.
Conclusion
It's desirable to pay attention to the speed of the tests from the very beginning of the development. Using simple methods you can get very fast tests and get maximum benefit from automatic testing.
live long and prosper 🖖