Создание и запуск тестов

Этот раздел состоит из двух разделов. В первом мы расскажем как писать тесты с Django, во втором - как их запускать

Написание тестов

Юнит тесты для Django используют стандартную библиотеку Python: unittest. Эта библиотека позволяет создавать тесты в ООП стиле.

В примере ниже мы наследуемся от django.test.TestCase, который является классом наследником unittest.TestCase, и запускает каждый тест в отдельной транзакции для их изоляции:

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    def setUp(self):
        Animal.objects.create(name="lion", sound="roar")
        Animal.objects.create(name="cat", sound="meow")

    def test_animals_can_speak(self):
        """Animals that can speak are correctly identified"""
        lion = Animal.objects.get(name="lion")
        cat = Animal.objects.get(name="cat")
        self.assertEqual(lion.speak(), 'The lion says "roar"')
        self.assertEqual(cat.speak(), 'The cat says "meow"')

Когда вы запускаете тесты, происходит поиск всех тестов (классов унаследованных от unittest.TestCase) в файлах, которые начинаются с test, затем создается набор тестов(test suite) и их запуск.

Подробности о unittest смотрите в документации Python.

Где должны находиться тесты?

Шаблон приложения, который использует команда startapp, содержит файл tests.py. Это подходит для приложений, которые содержат не много тестов. Но поре роста количества тестов вы захотите разделить их на модули, например test_models.py, test_views.py, test_forms.py, и т.д. Вы можете использовать любую удобную вам структуру.

Смотрите также Using the Django test runner to test reusable applications.

Предупреждение

Если ваши тесты используют базу данных, убедитесь, что вы наследуетесь от django.test.TestCase, а не unittest.TestCase.

При использовании unittest.TestCase экономится время на создание транзакции и сброса базы данных, но если тесты работают с базой данных, их поведение очень зависит от порядка выполнения тестов. В таком случае тесты, которые работают изолированно, могут не проходить при запуске вместе в другими тестами.

Запуск тестов

Чтобы запустить тесты, используйте команду test скрипта manage.py в вашей проекте:

$ ./manage.py test

Поиск тестов основан на поиске тестов библиотеки unittest. По умолчанию тесты ищутся во всех файлах “test*.py” в текущем каталоге.

Вы можете запустить определенные тесты, указав “test labels” для команды ./manage.py test. Каждый “label” - это путь к пакету Python, модулю, классу наследнику TestCase, или методу теста. Например:

# Run all the tests in the animals.tests module
$ ./manage.py test animals.tests

# Run all the tests found within the 'animals' package
$ ./manage.py test animals

# Run just one test case
$ ./manage.py test animals.tests.AnimalTestCase

# Run just one test method
$ ./manage.py test animals.tests.AnimalTestCase.test_animals_can_speak

Также можно передать путь к каталогу, в котором будет выполнен поиск тестов:

$ ./manage.py test animals/

Можно указать шаблон для поиска файлов с тестами, используя опцию -p (или --pattern), если имена ваших файлов с тестами не соответствуют шаблону test*.py:

$ ./manage.py test --pattern="tests_*.py"

Если нажать Ctrl-C при выполнении тестов, будет завершен текущий тест и затем остановлено выполнение тестов. После остановки будет выведен результат выполнения тестов и удалена тестовая база данных. Ctrl-C может быть очень полезна, если вы забыли указать опцию --failfast, и не хотите ждать окончания выполнения всех тестов, если один из них уже выполнился с ошибкой.

Если вы не хотите ждать окончания текущего теста, можете нажать Ctrl-C еще раз и выполнение тестов завершиться немедленно. При этом вы не получите результатов выполнения тестов и тестовая база данных не будет удалена.

Выполнение тестов с включенными предупреждениями

Хорошая практика - запускать тесты с включенными предупреждениями Python: python -Wall manage.py test. Флаг -Wall указывает Python показывать предупреждения об использовании устаревшего API. Django, как и многие другие библиотеки Python, использует такие предупреждения для пометки функционала, который в будущем будет удален из Django. Предупреждения могут также использоваться, чтобы обратить внимание на код, который не совсем неправильный, но требует лучшей реализации.

Тестовая база данных

Тесты, которые работают с базой данных (то есть тесты моделей), не будут использовать “настоящую” базу данных. Для этого будет создана отдельная пустая база данных.

Независимо от результата выполнения тестов тестовая база будет удалена после выполнения тестов.

Добавлено в Django 1.8:

Вы можете защитить тестовые базы данных от уничтожения, добавив флаг --keepdb к команде запуска теста. Это защитит тестовые базы данных между запусками. Если база данных не существует, сначала она будет создана. Любые миграции также будут применены в соответствующем порядке.

По умолчанию название тестовой базы данных формируется путем добавления префикса test_ к значению настройки NAME баз данных, указанных в настройке DATABASES. При использовании SQLite тестовая база данных создается в памяти (файловая система не используется!). Если вы хотите использовать другое название для тестовой базы данных, укажите его через NAME в словаре насйтроки TEST для баз данных, указанных в DATABASES.

Для PostgreSQL пользователь из USER должен иметь права на чтение встроенной базы данных postgres.

При создании тестовой базы данных будут использоваться настройки базы данных: ENGINE, USER, HOST, и т.д. Тестовая база данных создается пользователем из USER, убедитесь, что у этого пользователя есть права на создания базы данных.

Для больше контроля кодировок в тестовой базе данных используйте параметр CHARSET настройки TEST. При использовании MySQL можно также указать COLLATION. Подробнее о настройках можно прочитать в соответствующем разделе.

При использовании работающего в оперативной памяти SQLite совместно с Python 3.4+ и SQLite 3.7.13+, будет активирован разделяемый кэш, таким образом, вы сможете созавать тесты с возможностью разделения базы данных между потоками.

Добавлено в Django 1.8:

Была добавлена упомянутая выше возможность использования SQLite с разделяемым кэшем.

Запросы к настоящей базе дынных при выполнении тестов

Если ваш код выполняет запросы к базе данных при импорте модуля, эти запросы будут выполнены перед созданием тестовой базы данных, что может привести к неожиданным результатам. То есть такие запросы выполнятся для настоящей базы данных. В любом случае выполнение запросов при импорте модуля - плохая практика. Избегайте этого.

Это также относиться и к методу ready().

Порядок выполнения тестов

Чтобы код TestCase выполнялся на чистой базе данных, Django выполняет тесты в следующем порядке:

  • Сначала классы-наследники TestCase.

  • Затем остальные тесты, которые используют базовые тесты Django (классы-наследники SimpleTestCase, включая TransactionTestCase). Эти тесты могут быть выполнены в любом порядке.

  • Затем все остальные тесты unittest.TestCase (включая “doctests”), которые могут изменять базу данных без восстановления начального состояния.

Примечание

Такой порядок выполнения тестов может выявить неожиданные зависимости между тестами. Например, “doctests” могут использовать состояние, оставленное определенным TransactionTestCase. Такие тесты следует исправить, чтобы они могли выполняться независимо.

Добавлено в Django 1.8:

Вы можете обратить порядок исполнения внутри группы, передав флаг --reverse в команду запуска теста. Это может помочь в проверке того, что ваши тесты независимы друг от друга.

Эмуляция отката изменений

Любые начальные данные, созданные миграциями, будут доступны только в тестах TestCase, но не в тестах TransactionTestCase, и только, если база данных поддерживает транзакции (наиболее важным исключением будет MyISAM). Это также верно для тестов, которые основаны на TransactionTestCase, таких как LiveServerTestCase и StaticLiveServerTestCase.

Django может восстановить начальные данные перед каждым тестом, эмулируя откат изменений, если указать атрибут serialized_rollback с True в теле TestCase или TransactionTestCase. Но учтите, это замедлит выполнение тестов приблизительно в 3 раза.

Распространяемые приложения и те, что используют MyISAM, должны использовать этот атрибут. Но мы рекомендуем использовать базу данных, которая поддерживает транзакции, и TestCase для создания тестов. В таком случае эта настройка не понадобится.

Начальная сериализация данных обычно работает очень быстро, но если вы хотите исключить некоторые приложения из этого процесса (и немного ускорить выполнение тестов), вы можете указать их в настройке TEST_NON_SERIALIZED_APPS.

Изменено в Django 1.9.

Чтобы избежать повторной загрузки сериализованных данных, используйте параметр serialized_rollback=True, который отключает сигнал post_migrate при очистке тестовой базы данных.

Другие условия выполнения тестов

Независимо от значения DEBUG в вашем файле настроек, Django выполняет все тесты со значением DEBUG=False. Таким образом, при выполнении тестов код будет работать так же, как и на боевом сервере.

Кэш не очищается после каждого теста, и выполнение “manage.py test fooapp” может привести к добавлению тестовых данных в кэш на “боевом” сервере, если вы запускаете тесты на этот сервере. В отличии от базы данных, отдельный “кэш для тестирования” не используется. Но это может измениться в будущем.

Разбираем вывод в консоль при выполнении тестов

При выполнении тестов в консоль выводится различная информация. Вы можете указать уровень подробности вывода, используя опцию verbosity:

Creating test database...
Creating table myapp_animal
Creating table myapp_mineral

Этот вывод уведомляет, что была создана тестовая база данных, как описано выше.

После создания базы данных Django начнет выполнять тесты. Если тесты выполнены успешно, вы увидите приблизительно такое:

----------------------------------------------------------------------
Ran 22 tests in 0.221s

OK

Если некоторые тесты не выполнились успешно, будут выведены подробности:

======================================================================
FAIL: test_was_published_recently_with_future_poll (polls.tests.PollMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dev/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_poll
    self.assertEqual(future_poll.was_published_recently(), False)
AssertionError: True != False

----------------------------------------------------------------------
Ran 1 test in 0.003s

FAILED (failures=1)

Мы не будем подробно описывать информацию об ошибке, но все и так должно быть понятно. Подробности вы можете найти в документации Python для пакета unittest.

Обратите внимание, “return code” команды будет 1, если не выполнился хотя бы один тест. Если все тесты выполнились успешно, вернет 0. Это полезно, если вы запускаете тесты в shell скрипте и необходим результат выполнения.

Оптимизация тестов

В последней версии Django при генерации хэша для паролей используется медленный алгоритм. Если вы создаете большое количество пользователей в тестах, можно создать отдельный файл настроек и указать в PASSWORD_HASHERS более быстрый алгоритм:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.MD5PasswordHasher',
]

Не забудьте также добавить в PASSWORD_HASHERS все хэширующие алгоритмы, которые используются в фикстурах.