Простые советы по ускорению тестов Django

TDD (Test Driven Development) полезная и удобная практика для написания качественного кода. Согласно ей вы не можете писать код, не написав предварительно тесты для него.

При TDD тесты должны запускаться как можно чаще и, следовательно, они должны отрабатывать как можно быстрее, чтоб исключить пустую потерю времени на ожидание.

В данном посте я перечислю некоторые приемы, которые помогут вам значительно ускорить выполнение тестов Django. На момент написания статьи выполнение набора из 250 тестов занимало около 5 секунд, по сравнению 50 секундами на выполнения тех же тестов, но еще не оптимизированых. Т.е. это в 10 раз быстрее.

Итак приступим.

Измените метод хеширования паролей

Это наиболее эффективный способ ускорения ваших тестов, как ни странно. В Django по-умолчанию используется сильная защита паролей, с использованием нескольких алгоритмов хеширования. И как следствие, она довольно медленная. Самый быстрый метод хеширования - MD5PasswordHasher. Он не самый надежный, но для тестов в самый раз. Установить его можно следующим способом:

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

Используйте быстрое хранилище данных

На данный момент самое быстрое хранилище, которое можно использовать в Django - это SQLite. Изначально я сомневался в возможности замены текущей базы данных (Postgres) на SQLite для тестов. Но, поскольку, я не тестирую код самого Django и не использую прямые sql запросы, то такая замена никак не повлияла на функционирование кода. Поэтому настроить SQLite для тестирования можно следующим образом:

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

Удалите ненужные middleware

Middleware - одна из любимых фич Django, которую я использую. Но чем больше их используется, тем больше время ответа. Поскольку все middleware должны быть выполнены в определенном порядке до получения итогового объекта HTTPResponse. Всегда. Поэтому старайтесь при тестировании подключать только те их них, которые реально используются и влияют на результаты тестируемых областей кода.

Одно из middleware особенно медленное, поэтому я всегда стараюсь отключать при тестах:

django.middleware.locale.LocaleMiddleware

Удалите неиспользуемые приложения

Некоторые сторонние библиотеки и модули могут быть смело отключены на время тестирования. Например, django-debug-toolbar.

Отключите режим дебага

Поскольку при тестировании нам не требуется расширенная отладочная информация, то режим дебага можно отключить:

DEBUG = False
TEMPLATE_DEBUG = False

Отключите логирование

Этот пункт нужен в случае, если вы логируете очень много и/или при логировании используется дополнительная сложная логика (испектирование объектов, большие манипуляции со строками и т.п.). В любом случае логирование при тестировании бесполезно, поэтому:

logging.disable(logging.CRITICAL)

Используйте ускоренный email backend

По-умолчанию Django использует django.core.mail.backends.locmem.EmailBackend, который расчитан для хранения писем в памяти на время тестирования. Но в нем все же есть проблемы. Иначе как объяснить потерю около 30 секунд на проверку заголовков писем. Поэтому решено было поправить этот backend, убрав из него провеку заголовков и тем самым значительно его ускорив:

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

class TestEmailBackend(BaseEmailBackend):

    def send_messages(self, messages):
        mail.outbox.extend(messages)
        return len(messages)

Для использования его нам понадобится декоратор @override_settings, поскольку определение нового backend в файле настроек не помогает - Django продолжает использовать django.core.mail.backends.locmem.EmailBackend при тестировании.

Поэтому, следую философии DRY, создадим подкласс для django.test.TestCase, который будет включать переопреденение настроек:

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

@override_settings(EMAIL_BACKEND='myapp.TestEmailBackend')
class TestCase(DjangoTestCase):
    pass

Используйте метод bulk_create()

Если вы создаете много объектов за раз, то не стоит для каждого вызывать метод save(). Вместо этого воспользуйтесь методом bulk_create(), который предоставляется Django ORM для добавления нескольких записей одним запросом.

Используйте in-memory backend для Celery

Если вы используйте очередь задач Celery, то наиболее оптимальные настройки:

CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
BROKER_BACKEND = 'memory'

На этом пока все.

 
comments powered by Disqus