Effective TDD: Tricks to speed up Django tests (up to 10x faster!)

TDD (Test Driven Development) is one of the best practices to follow in order to write better software and once you get used to this practice you can’t write code without writing tests.
In TDD, tests must be executed very frequently and so it’s fundamental that they run as fast as possible in order to avoid time wasting.
In this post I want to share all the tricks I discovered that will make your Django tests run very quickly (at the time of this writing I have a suite of 250 tests that has been tuned to run in ~5 seconds rather than ~50 seconds of the untuned version… that’s 10 times faster!)
In this article I’m assuming that you know: how to run Django unit tests, how to use a different settings module for testing.
These are my tips:

1) Change password hashing

This is the most effective setting you can use to improve the speed of tests, it may sounds ridicolous, but password hashing in Django is designed to be very strong and it makes use of several “hashers”, but this also means that the hashing is very slow. The fastest hasher is the MD5PasswordHasher, so you can use just that one in this way:


2) Use a faster storage system

Currently the fastest database you can use with Django is SQLite. I was initially doubtful about switching from Postgres (my database of choice) to SQLite, but I’m not testing Django itself, I’m testing my own API and since I don’t use raw SQL statements the underlying storage backend should not make differences!
So, to configure SQLite:

    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'simple_test_db'

3) Remove unnecessary middlewares

Middleware layer is one of the Django features that I like the most, BUT the more middleware classes you use, the more your response time will increase (since all the middleware must be executed sequentially before returning the final HTTPResponse, ALWAYS!). So be sure to include only the stuff you really and absolutely need!
One middleware in particular that is very slow and that I removed for the tests is:


4) Remove unnecessary apps

There are several third party apps that you may remove during testing like the debug toolbar, try to remove all the unused/unnecessary apps.

5) Disable debugging

We don’t need extended traceback during tests, so we can disable debugging:

DEBUG = False

6) Turn off logging

This is a significant modification only if we have a huge amount of logging and/or additional logic involved in logs (such object inspections, heavy string manipulation and so on), but anyway logging is futile during testing, so:


7) Use a faster Email backend (by “patching” Django)

By default Django will use django.core.mail.backends.locmem.EmailBackend, which is an in-memory backend designed for testing, however I had several problems with that backend during my tests, they did block unexplainably for ~30 seconds due to headers checking. So I decided to write my own in-memory backend which mimics the Django one but does not check email headers in order to be blazing fast:

from django.core.mail.backends.base import BaseEmailBackend
from django.core import mail

class TestEmailBackend(BaseEmailBackend):

    def send_messages(self, messages):
        return len(messages)

In order to use this backend I had to use the @override_settings decorator, since defining it in settings file is helpless because that asshole of Django (1.5.x) will use django.core.mail.backends.locmem.EmailBackend anyway!
So, by following DRY phylosophy I created a subclass of Django’s TestCase which includes that overridden setting (so I don’t have to remember to define my own backend for each test):

from django.test import TestCase as DjangoTestCase
from django.test.utils import override_settings

class TestCase(DjangoTestCase):

8) Make use of bulk_create

If you are creating more objects don’t rely on save() for each object, but instead create objects in batch using bulk_create() (it’s a method provided by Django ORM to insert multiple records in a single query)

9) Use an in-memory backend for Celery

If you are using Celery, these are my optimal settings for testing:


That’s all! If you have other advices please let me know ;)

  • Kern3l

    2) You may be surprised that on production suddenly some tests won’t pass. You have to test it all on production settings before deploying. However, best practice is to avoid changing db backend if it’s not absolutely necessary, to find such bugs immediately.

    Anyway, thanks for this article!

  • @Kern3l In my opinion if you are not using db-related features (features that are available in a specific db), it’s ok to switch to a faster db for tests, since the part of testing db interaction with python code it’s up to Django tests, not yours.

  • I was using an in-memory sqlite database in my test setting which was very fast. But my test with production database (postgresql) was failing raising IntegrityError in fixtures.

  • #sadness :( …anyway I’m currently using Postgres for my tests since I use GeoDjango :P

  • dragonx

    @DavideZanotti try spatialite for testing.

  • Esteban

    Keeping your templates in memory may improve performance (this is good for production and testing but not for developing).

    (‘django.template.loaders.cached.Loader’, (

  • Why not use https://docs.djangoproject.com/en/1.7/topics/email/#dummy-backend?

    EMAIL_BACKEND = “django.core.mail.backends.dummy.EmailBackend”

  • Because I’m dumb… thanks for the tip ;^)

  • César Alejandro Lara

    Love you brah. Testing can be very painful because of the time.

  • Using SQLite for testing really speeds things up for me. Be careful using a different DB in production though, since there may be differences.

  • I had the same issue :( any alternative?

  • Anass

    The first trick was really helpful man. Thanks for writing this !

  • Travis Cunningham

    This is definitely a downside of using sqlite in a test environment, since Postgresql behaves differently. Perhaps running Sqlite locally but using a “real” database on your continuous integration system would help here.