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

Этот документ разделен на два основных раздела. Сначала мы объясним, как писать тесты с помощью 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"')

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

Более подробную информацию о unittest смотрите в документации по Python.

Где должны проводиться тесты?

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

См. также Использование бегуна тестирования Django для тестирования многократно используемых приложений.

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

Если ваши тесты полагаются на доступ к базе данных, например, создание или запрос моделей, обязательно создавайте классы тестов как подклассы django.test.TestCase, а не unittest.TestCase.

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

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

После того, как вы написали тесты, запустите их с помощью команды test утилиты manage.py вашего проекта:

$ ./manage.py test

Обнаружение тестов основано на модуле unittest built-in test discovery. По умолчанию он обнаруживает тесты в любом файле с именем «test*.py» в текущем рабочем каталоге.

Вы можете указать конкретные тесты для запуска, предоставив любое количество «тестовых меток» в ./manage.py test. Каждая метка теста может быть полным пунктирным путем 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 -Wa manage.py test. Флаг -Wa указывает Python на отображение предупреждений об износе. Django, как и многие другие библиотеки Python, использует эти предупреждения, чтобы отметить, когда функции исчезают. Он также может отметить места в вашем коде, которые не являются строго неправильными, но могут выиграть от лучшей реализации.

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

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

Независимо от того, прошли тесты или нет, базы данных тестов уничтожаются после выполнения всех тестов.

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

Как описано в предыдущем разделе, если выполнение теста принудительно прервано, база данных теста может быть не уничтожена. При следующем запуске вам будет задан вопрос, хотите ли вы повторно использовать или уничтожить базу данных. Используйте опцию test --noinput, чтобы подавить этот запрос и автоматически уничтожить базу данных. Это может быть полезно при выполнении тестов на сервере непрерывной интеграции, где тесты могут быть прерваны, например, по таймауту.

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

На PostgreSQL, USER также потребуется доступ на чтение к встроенной базе данных postgres.

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

Для тонкого контроля над кодировкой символов в тестовой базе данных используйте опцию CHARSET TEST. Если вы используете MySQL, вы также можете использовать опцию COLLATION для управления конкретной кодировкой, используемой тестовой базой данных. Подробнее об этих и других дополнительных настройках см. в settings documentation.

При использовании базы данных in-memory с SQLite включается shared cache, поэтому вы можете писать тесты с возможностью совместного использования базы данных между потоками.

Поиск данных из производственной базы данных при выполнении тестов?

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

Это также относится к индивидуальным реализациям ready().

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

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

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

Примечание

Новое упорядочивание тестов может выявить неожиданные зависимости от упорядочивания тестовых примеров. Это касается доктестов, которые полагались на состояние, оставленное в базе данных данным тестом TransactionTestCase, они должны быть обновлены, чтобы иметь возможность запускаться независимо.

Примечание

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

Вы можете рандомизировать и/или изменить порядок выполнения внутри групп с помощью опций test --shuffle и --reverse. Это может помочь обеспечить независимость ваших тестов друг от друга.

Changed in Django 4.0:

В старых версиях сбои, обнаруженные при загрузке тестов, не были упорядочены в первую очередь.

Эмуляция отката

Любые начальные данные, загруженные в миграциях, будут доступны только в тестах 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. Это делается для того, чтобы гарантировать, что наблюдаемый вывод вашего кода соответствует тому, что будет наблюдаться в производственных условиях.

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

Понимание результатов тестирования

Когда вы запускаете свои тесты, вы увидите ряд сообщений по мере подготовки программы запуска тестов. Вы можете контролировать уровень детализации этих сообщений с помощью опции 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.

Обратите внимание, что код возврата для сценария test-runner равен 1 при любом количестве неудачных и ошибочных тестов. Если все тесты пройдены, код возврата равен 0. Эта возможность полезна, если вы используете сценарий test-runner в сценарии оболочки и вам нужно проверить успех или неудачу на этом уровне.

Ускорение тестирования

Параллельное выполнение тестов

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

Хеширование паролей

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

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

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

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

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

Вернуться на верх