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

Этот раздел состоит из двух разделов. В первом мы расскажем как писать тесты с 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 еще раз и выполнение тестов завершиться немедленно. При этом вы не получите результатов выполнения тестов и тестовая база данных не будет удалена.

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

It’s a good idea to run your tests with Python warnings enabled: python -Wa manage.py test. The -Wa flag tells Python to display deprecation warnings. Django, like many other Python libraries, uses these warnings to flag when features are going away. It also might flag areas in your code that aren’t strictly wrong but could benefit from a better implementation.

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

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

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

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

As described in the previous section, if a test run is forcefully interrupted, the test database may not be destroyed. On the next run, you’ll be asked whether you want to reuse or destroy the database. Use the test --noinput option to suppress that prompt and automatically destroy the database. This can be useful when running tests on a continuous integration server where tests may be interrupted by a timeout, for example.

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

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

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

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

If using an SQLite in-memory database with SQLite, shared cache is enabled, so you can write tests with ability to share the database between threads.

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

Caches are not cleared after each test, and running «manage.py test fooapp» can insert data from the tests into the cache of a live system if you run your tests in production because, unlike databases, a separate «test cache» is not used. This behavior may change in the future.

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

При выполнении тестов в консоль выводится различная информация. Вы можете указать уровень подробности вывода, используя опцию 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.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False

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

FAILED (failures=1)

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

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

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

Запуск тестов параллельно

Если ваши тесты хорошо изолированны, вы можете запускать их параллельно, чтобы получить преимущество в скорости при использовании многоядерных процессоров. Смотрите test --parallel.

Хэширование пароля

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

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

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

Сохранение тестовой базы данных

Опция test --keepdb сохраняет тестовую базу данных между запусками тестов. Пропускается создание и удаление базы данных, что может значительно ускорить выполнение тестов.