Написание и выполнение тестов
См. также
Учебник по тестированию, справочник по инструментам тестирования и расширенные темы тестирования.
Этот документ разделён на два основных раздела. Сначала мы объясним, как писать тесты с 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, автоматически создаёт из них тестовый набор и выполняет этот набор.
Дополнительную информацию о unittest можно найти в документации Python.
Где должны храниться тесты?
Шаблон по умолчанию startapp создаёт файл tests.py в новом приложении. Это может подойти, если у вас всего несколько тестов, но по мере роста набора тестов вы, скорее всего, захотите перестроить его в пакет tests, чтобы разделить тесты на разные подмодули, такие как test_models.py, test_views.py, test_forms.py и т. д. Вы можете выбрать любую схему организации.
См. также Использование исполнителя тестов Django для тестирования повторно используемых приложений.
Предупреждение
Если ваши тесты полагаются на доступ к базе данных, например, на создание или запросы моделей, убедитесь, что ваши классы тестов являются подклассами django.test.TestCase, а не unittest.TestCase.
Использование unittest.TestCase избегает затрат на выполнение каждого теста в транзакции и сброс базы данных, но если ваши тесты взаимодействуют с базой данных, их поведение будет меняться в зависимости от порядка, в котором их выполняет инструмент запуска тестов. Это может привести к тому, что модульные тесты пройдут при запуске изолированно, но потерпят неудачу при запуске в наборе.
Запуск тестов
После написания тестов, запустите их, используя команду test утилиты проекта manage.py:
$ ./manage.py test
Обнаружение тестов основано на встроенном механизме обнаружения тестов модуля unittest встроенное обнаружение тестов. По умолчанию, это обнаружит тесты в любом файле с именем test*.py в текущей рабочей директории.
Вы можете указать конкретные тесты для запуска, передав любое количество «метки тестов» команде ./manage.py test. Каждая метка теста может быть полным пути к пакету, модулю, классу 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 class $ ./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. Если вы используете MySQL, вы также можете использовать опцию COLLATION для управления конкретной сортировкой, используемой тестовой базой данных. Подробности этих и других расширенных параметров см. в документации по настройкам.
При использовании SQLite базы данных в памяти с SQLite, кэширование совместно используется, поэтому вы можете писать тесты с возможностью совместного использования базы данных между потоками.
Получение данных из вашей производственной базы данных при запуске тестов?
Если ваш код пытается получить доступ к базе данных во время компиляции модулей, это произойдёт *до* создания тестовой базы данных, что может привести к непредсказуемым результатам. Например, если в коде модуля есть запрос к базе данных и реальная база данных существует, данные из производственной базы данных могут испортить ваши тесты. *В любом случае, наличие таких запросов к базе данных в вашем коде на этапе импорта — плохая практика*. Перепишите ваш код так, чтобы этого не происходило.
Это также относится к настраиваемым реализациям ready().
См. также
Раздел расширенные темы тестирования с несколькими базами данных.
Порядок выполнения тестов
Для обеспечения того, что весь TestCase код запускается с чистой базой данных, Django запустивший тесты инструмент изменяет порядок тестов следующим образом:
- Сначала выполняются все подклассы
TestCase. - Затем все остальные тесты Django (классы тестовых случаев, основанные на
SimpleTestCase, включаяTransactionTestCase) выполняются без гарантированного или навязанного порядка между ними. - Затем выполняются любые другие тесты
unittest.TestCase(включая doctests), которые могут изменять базу данных без восстановления ее в исходное состояние.
Примечание
Новый порядок выполнения тестов может раскрыть неожиданные зависимости от порядка тестовых случаев. Это относится к doctests, которые зависели от состояния базы данных, оставленного определенным тестом TransactionTestCase. Они должны быть обновлены, чтобы они могли выполняться независимо.
Примечание
Ошибки, обнаруженные при загрузке тестов, упорядочиваются перед всем вышеперечисленным для более быстрого получения обратной связи. Это включает в себя такие вещи, как тестовые модули, которые не были найдены или которые не могли быть загружены из-за синтаксических ошибок.
Вы можете случайным образом и/или изменить порядок выполнения внутри групп с помощью опций test --shuffle и --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 при сбросе тестовой базы данных.
Для TransactionTestCase сериализованные данные миграции становятся доступными во время setUpClass().
Другие условия тестирования
Независимо от значения параметра 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.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
Полное объяснение этого вывода об ошибках выходит за рамки этого документа, но оно довольно интуитивно понятное. Вы можете обратиться к документации Python's unittest для получения подробностей.
Обратите внимание, что код возврата скрипта тестового запуска равен 1 при любом количестве неудачных тестов (будь то ошибка, сбой утверждения или неожиданный успех). Если все тесты пройдены, код возврата равен 0. Эта функция полезна, если вы используете скрипт тестового запуска в скрипте оболочки и вам нужно проверить успех или неудачу на этом уровне.
Ускорение тестов
Запуск тестов параллельно
До тех пор, пока ваши тесты должным образом изолированы, вы можете запускать их параллельно, чтобы ускорить выполнение на оборудовании с несколькими ядрами. См. test --parallel.
Хэширование паролей
По умолчанию хэшер паролей довольно медленный по своей конструкции. Если вы аутентифицируете много пользователей в своих тестах, вам может потребоваться использовать файл пользовательских настроек и установить параметр PASSWORD_HASHERS на более быстрый алгоритм хэширования:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
Не забудьте также включить в PASSWORD_HASHERS любой алгоритм хэширования, используемый в фикстурах, если таковые имеются.
Сохранение тестовой базы данных
Параметр test --keepdb сохраняет тестовую базу данных между запусками тестов. Он пропускает действия создания и уничтожения, что может значительно сократить время выполнения тестов.
Избегание доступа к диску для медиафайлов
Класс InMemoryStorage — удобный способ предотвратить доступ к диску для медиафайлов. Все данные хранятся в памяти, а затем удаляются после выполнения тестов.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/testing/overview/