Optimizing your tests in Django

Tips to make your unit tests run faster

Intro

Unit tests must be fast. With time, as a project grows, the number of unit tests grows proportionally.

In your everyday work you need to run those tests. Maybe not all of them, all the time. But very often you do have to run all of them. So, you don’t want to end up waiting for an eternity.

In one of our Django-1.5 projects, after one year of work, the test suite included over 1800 unit tests running in 62 seconds. That is quite an acceptable time.

Therefore, in this post I want to share a couple of tips that helped us a lot to optimize the time of our test suite.

I won’t be talking about unit testing in general or how to write good unit tests. Most of these tips work by using custom settings when running the tests. Something like the following in the settings.py file should be enough (spoiler alert!):

if 'test' in sys.argv:
    DATABASES['default'] = {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'tests.db',
    }

Tips

In-memory DB

The fastest DB to run the tests is SQLite. If this engine is set when testing an in-memory DB is used (https://docs.djangoproject.com/en/1.5/ref/settings/#test-name).

In any case, this may cause that you miss DBMS-specific issues. So you either test with the real DBMS every once in a while, or write specific tests for those issues (the override_settings decorator may prove useful).

UPDATE: This tip is very controversial. In Two Scoops of Django there’s a whole section (2.1 Use the Same Database Engine Everywhere) against it.

Password hashers

As a design decision (for security reasons) password hashing is a slow process. So, speeding up this process usually has a big impact in the tests. This is mentioned in Django’s documentation. In our case, when testing we use a PlainPasswordHasher

if 'test' in sys.argv:
    PASSWORD_HASHERS = ('django_plainpasswordhasher.PlainPasswordHasher', )

File storage

If your project manages MEDIA (like files uploaded by the users) it is a useful idea to use an in-memory file storage when running the tests. For example django-inmemorystorage.

if 'test' in sys.argv:
    DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'

Mock external APIs

If your applications uses external services (such as search engines and message queues) or remote resources (such as e-commerce tools, social-networks APIs, CRM platforms, etc), it is a good idea to mock or disable them and only activate them as needed.

This is the case even when such resource provides specific test facilities. For example we used Stripe to handle payments. They have a cool API for testing. But as we started using their API, the speed of the tests became too slow. So we added an ALLOW_STRIPE_TESTING setting and then isolated and decorated the Stripe-specific tests with @skipUnless(settings.ALLOW_STRIPE_TESTING). Now we can enable those specific tests when required.

... and more

Those are the things that helped us the most. Other things that are recommended but didn’t have much impact in our case include disabling the logging and removing middlewares or apps not needed while testing.


Previous / Next posts


Comments