Юнит тесты¶
Django поставляется с собственным набором тестов в каталоге tests кодовой базы. Наша политика заключается в том, чтобы все тесты всегда проходили успешно.
Мы ценим любой вклад в тест кейсы!
Все тесты Django используют инфраструктуру тестирования, которая поставляется с Django. См. Создание и запуск тестов для объяснения того, как писать новые тесты.
Выполнение модульных тестов¶
Быстрый старт¶
Сначала создайте форк Django на GitHub.
Затем, создайте и активируйте виртуальную среду. Если вы не знакомы с тем, как это сделать, прочитайте наше руководство по содействию.
Далее клонируйте свою вилку, установите некоторые требования и запустите тесты:
$ git clone https://github.com/YourGitHubName/django.git django-repo
$ cd django-repo/tests
$ python -m pip install -e ..
$ python -m pip install -r requirements/py3.txt
$ ./runtests.py
...\> git clone https://github.com/YourGitHubName/django.git django-repo
...\> cd django-repo\tests
...\> py -m pip install -e ..
...\> py -m pip install -r requirements\py3.txt
...\> runtests.py
Установка зависимостей, вероятно, потребует некоторых пакетов, которые не установлены на вашем компьютере. Обычно вы можете выяснить, какой пакет установить, выполнив поиск в Интернете по последней строке или около сообщения об ошибке. Добавьте название своей операционной системы в поисковый запрос, если необходимо.
Если у вас возникли проблемы с установкой зависимостей, вы можете пропустить этот шаг. См. Выполнение всех тестов для получения подробной информации об установке необязательных зависимостей для тестов. Если у вас не установлена необязательная зависимость, тесты, которым она требуется, будут пропущены.
Для запуска тестов требуется модуль настроек Django, который определяет используемые базы данных. Чтобы помочь вам начать работу, Django предоставляет пример модуля настроек, который использует базу данных SQLite. См. Использование другого модуля settings, чтобы узнать, как использовать другой модуль настроек для запуска тестов с другой базой данных.
Возникли проблемы? Смотрите Поиск неисправностей для некоторых распространенных проблем.
Запуск тестов с использованием tox¶
Tox — это инструмент для запуска тестов в различных виртуальных средах. Django включает в себя базовый tox.ini, который автоматизирует некоторые проверки, которые наш сервер сборки выполняет в запросах на приниятие изменений. Для запуска юнит тестов и других проверок (таких как сортировка импортов, проверка грамматики в документации и форматирование кода), установите и запустите команду tox из любого места в директории Django:
$ python -m pip install tox
$ tox
...\> py -m pip install tox
...\> tox
По умолчанию tox запускает набор тестов со встроенным файлом настроек тестов для SQLite, blacken-docs, flake8, isort, lint-docs, zizmor и средства проверки орфографии документации. В дополнение к системным зависимостям, указанным в других разделах этой документации, команда python3 должна находиться в вашем пути и быть связана с соответствующей версией Python. Список сред по умолчанию можно увидеть следующим образом:
$ tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=7.0.0
lint-docs
zizmor>=1.16.3
...\> tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=7.0.0
lint-docs
zizmor>=1.16.3
Тестирование для других версий Python и бэкэндов баз данных¶
В дополнение к средам по умолчанию, tox поддерживает запуск модульных тестов для других версий Python и других баз данных. Однако, поскольку набор тестов Django не включает файл настроек для серверов баз данных, отличных от SQLite, вы должны создать и предоставить свои собственные настройки теста. Например, чтобы запустить тесты на Python 3.12 с использованием PostgreSQL:
$ tox -e py312-postgres -- --settings=my_postgres_settings
...\> tox -e py312-postgres -- --settings=my_postgres_settings
Эта команда настраивает виртуальную среду Python 3.12, устанавливает зависимости набора тестов Django (в том числе для PostgreSQL) и вызывает runtests.py с предоставленными аргументами (в данном случае --settings=my_postgres_settings).
В оставшейся части этой документации показаны команды для запуска тестов без tox, однако любой параметр, переданный в runtests.py, также может быть передан в tox, если добавить к списку аргументов префикс --, как указано выше.
Tox также учитывает переменную окружения DJANGO_SETTINGS_MODULE, если она установлена. Например, следующее эквивалентно команде выше:
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py312-postgres
Пользователям Windows следует использовать:
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py312-postgres
Выполнение тестов JavaScript¶
Django включает набор юнит тестов для JavaScript для функций в некоторых contrib-приложениях. Тесты JavaScript не запускаются по умолчанию с использованием tox, поскольку для них требуется установка Node.js и не являются необходимыми для большинства патчей. Чтобы запустить тесты JavaScript с использованием tox:
$ tox -e javascript
...\> tox -e javascript
Эта команда запускает npm install, чтобы убедиться, что требования для тестов актуальны а затем запускает npm test.
Запуск тестов с использованием django-docker-box¶
django-docker-box позволяет запускать набор тестов Django во всех поддерживаемых базах данных и версиях Python. Инструкции по установке и использованию см. на странице проекта django-docker-box
Использование другого модуля settings¶
Модуль настроек по-умолчанию (tests/test_sqlite.py) позволяет вам запускать тестовый набор с использованием SQLite. Если вы хотите запустить тесты с использованием другой базы данных, вам нужно будет определить свой собственный файл настроек. Некоторые тесты, такие как для contrib.postgres, специфичны для конкретной базы данных и будут пропущены, если будут запущены с другим бэкэндом. Некоторые тесты пропускаются или ожидают сбоев на конкретной базе данных (см. DatabaseFeatures.django_test_skips и DatabaseFeatures.django_test_expected_failures на каждом бэкэнде).
Чтобы запустить тесты с разными настройками, убедитесь, что модуль находится в вашей PYTHONPATH и передайте модуль с помощью --settings.
Настройка DATABASES в любом модуле настроек теста должна определять две базы данных:
База данных
default. Эта база данных должна использовать бэкэнд, который вы хотите использовать для первичного тестирования.База данных с псевдонимом
other. База данныхotherиспользуется для проверки того, что запросы могут быть направлены в разные базы данных. Эта база данных должна использовать тот же бэкэнд, что иdefault, и у нее должно быть другое имя.
Если вы используете бэкэнд, отличный от SQLite, вам нужно будет предоставить другие данные для каждой базы данных:
В параметре
USERнеобходимо указать существующую учетную запись пользователя для базы данных. Этому пользователю необходимо разрешение на выполнениеCREATE DATABASE, чтобы можно было создать тестовую базу данных.Опция
PASSWORDдолжна предоставить пароль дляUSER, который был указан.
Тестовые базы данных получают свои имена путем добавления test_ к значению :настроек настройки:NAME для баз данных, определенных в DATABASES. Эти тестовые базы данных удаляются после завершения тестов.
Вам также необходимо убедиться, что ваша база данных использует UTF-8 в качестве набора символов по умолчанию. Если ваш сервер базы данных не использует UTF-8 в качестве набора символов по умолчанию, вам необходимо включить значение для CHARSET в словарь настроек теста для соответствующей базы данных.
Выполнение только некоторых тестов¶
Выполнение всего набора тестов Django занимает некоторое время, и выполнение каждого отдельного теста может быть излишним, если, скажем, вы просто добавили тест в Django, который вы хотите запустить быстро, не запуская все остальное. Вы можете запустить подмножество юнит тестов, добавив имена тестовых модулей в runtests.py в командной строке.
Например, если вы хотите запустить тесты только для общих функций и интернационализации, введите:
$ ./runtests.py --settings=path.to.settings generic_relations i18n
...\> runtests.py --settings=path.to.settings generic_relations i18n
Как узнать названия отдельных тестов? Посмотрите в tests/ — каждое имя каталога там — название теста.
Если вы хотите запустить только определенный класс тестов, вы можете указать список путей к отдельным классам тестов. Например, чтобы запустить TranslationTests модуля i18n, введите:
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests
Выйдя за рамки этого, вы можете указать индивидуальный метод тестирования, например:
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
Вы можете запустить тесты, начиная с указанного модуля верхнего уровня с помощью опции --start-at. Например:
$ ./runtests.py --start-at=wsgi
...\> runtests.py --start-at=wsgi
Вы также можете запустить тесты, начиная с указанного модуля верхнего уровня с помощью опции --start-after. Например:
$ ./runtests.py --start-after=wsgi
...\> runtests.py --start-after=wsgi
Обратите внимание, что опция --reverse не влияет на опции --start-at или --start-after. Более того, эти опции нельзя использовать с тестовыми метками.
Выполнение тестов Selenium¶
Некоторые тесты требуют Selenium и веб-браузер. Для запуска этих тестов необходимо установить пакет selenium и запустить тесты с параметром --selenium=<BROWSERS>. Например, если у вас установлены Firefox и Google Chrome:
$ ./runtests.py --selenium=firefox,chrome
...\> runtests.py --selenium=firefox,chrome
Список доступных браузеров см. в пакете selenium.webdriver.
Указание --selenium автоматически устанавливает --tags=selenium для запуска только тестов, которым требуется Selenium.
Некоторые браузеры (например, Chrome или Firefox) поддерживают headless-тестирование, которое может быть быстрее и стабильнее. Добавьте опцию --headless, чтобы включить этот режим.
Скриншот-тесты¶
Для тестирования изменений в пользовательском интерфейсе администратора тесты Selenium можно запустить с включенной опцией --screenshots. Скриншоты будут сохранены в каталоге tests/screenshots/.
Чтобы определить, когда следует делать снимки экрана во время теста Selenium, тестовый класс должен использовать декоратор @django.test.selenium.screenshot_cases со списком поддерживаемых типов снимков экрана ("desktop_size", "mobile_size", "small_screen_size", "rtl", "dark", и "high_contrast"). Затем он может вызвать self.take_screenshot("unique-screenshot-name") в нужное время для создания снимков экрана. Например, example:
from django.test.selenium import SeleniumTestCase, screenshot_cases
from django.urls import reverse
class SeleniumTests(SeleniumTestCase):
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
def test_login_button_centered(self):
self.selenium.get(self.live_server_url + reverse("admin:login"))
self.take_screenshot("login")
...
Это создает несколько снимков экрана страницы входа — один для экрана настольного компьютера, один для экрана мобильного устройства, один для языков с письмом справа налево на настольном компьютере, один для темного режима на настольном компьютере и один для режима высокой контрастности на настольном компьютере при использовании Chrome.
Выполнение всех тестов¶
Если вы хотите запустить полный набор тестов, вам нужно установить ряд зависимостей:
aiosmtpd 1.4.5+
argon2-cffi 23.1.0+
asgiref 3.9.1+ (обязательно)
bcrypt 4.1.1+
colorama 0.4.6+
docutils 0.22+
geoip2 4.8.0+
Jinja2 2.11+
numpy 1.26.0+
Подушка 10.1.0+
PyYAML 6.0.2+
redis 5.1.0+
pymemcache, а также `поддерживаемый привязка Python <https://memcached.org/
селен 4.23.0+
sqlparse 0.5.0+ (обязательно)
tblib 3.0.0+
Вы можете найти эти зависимости в файлах зависимостей pip внутри каталога tests/requirements исходного кода Django и установить их следующим образом:
$ python -m pip install -r tests/requirements/py3.txt
...\> py -m pip install -r tests\requirements\py3.txt
Если во время установки вы столкнулись с ошибкой, возможно, в вашей системе отсутствует зависимость для одного или нескольких пакетов Python. Обратитесь к документации по неисправному пакету или выполните поиск в Интернете по сообщению об ошибке, с которым вы столкнулись.
Вы также можете установить адаптер(ы) базы данных по вашему выбору с помощью oracle``txt, mysql.txt или postgres.txt.
Если вы хотите протестировать бэкэнды кэша memcached или Redis, вам также нужно будет определить настройку CACHES, которая указывает на ваш экземпляр memcached или Redis соответственно.
Для запуска тестов GeoDjango вам необходимо настроить пространственную базу данных и установить гео-библиотеки.
Каждая из этих зависимостей является необязательной. Если вы пропустите какую-либо из них, соответствующие тесты будут пропущены.
Чтобы запустить некоторые тесты автоперезагрузки, вам необходимо установить Watchman.
Покрытие кода¶
Участникам рекомендуется запустить покрытие в наборе тестов, чтобы определить области, требующие дополнительных тестов. Установка и использование инструмента покрытия описаны в testing code coverage.
Чтобы запустить тестирование набора тестов Django с использованием стандартных настроек теста:
$ coverage run ./runtests.py --settings=test_sqlite
...\> coverage run runtests.py --settings=test_sqlite
После запуска покрытия объедините всю статистику покрытия, выполнив:
$ coverage combine
...\> coverage combine
После этого сгенерируйте HTML-отчет, выполнив:
$ coverage html
...\> coverage html
При запуске покрытия для тестов Django, файл настроек .coveragerc определяет coverage_html как выходной каталог для отчета, а также исключает несколько каталогов, не имеющих отношения к результатам (тестовый код или код, включенный в Django).
Покрытие кода в запросах на извлечение¶
Система непрерывной интеграции (CI) Django автоматически запускает анализ покрытия кода по запросам на включение и публикует комментарий с отчетом о различном покрытии. Это помогает рецензентам увидеть, какие строки измененного кода охвачены тестами.
Что показано в отчете об охвате:
Отчет о покрытии, публикуемый в запросах на включение, использует diff-cover для анализа только тех строк, которые были изменены или добавлены в PR. Он показывает:
Линии, охваченные тестами (✓)
Линии, не охваченные тестами (✗)
Строки, которые нельзя закрыть (например, комментарии, пустые строки).
Важные ограничения:
При просмотре отчетов о покрытии запросов на включение имейте в виду следующие ограничения:
Код для конкретной базы данных: Задание покрытия ЭК запускает тесты с использованием SQLite в Windows. Пути кода, специфичные для других баз данных (PostgreSQL, MySQL, Oracle), будут отображаться как «не охваченные», даже если существуют тесты для конкретной базы данных. Это ожидаемо и приемлемо.
Код для конкретной платформы. Аналогично, код, который работает только в определенных операционных системах (Linux, macOS), при запуске в Windows будет отображаться как не охваченный.
Покрытие не равно качеству: «Покрытие» строки означает лишь то, что она была выполнена во время тестов. Это не гарантирует, что линия хорошо протестирована или что все крайние случаи обработаны. Во время проверки оценивайте качество тестирования, а не только показатели охвата.
Отсутствие освещения следует рассматривать как предупреждение, а не как препятствие, и его следует оценивать в контексте.
Приложения contrib¶
Тесты для contrib-приложений можно найти в каталоге tests/, обычно в <app_name>_tests. Например, тесты для contrib.auth находятся в tests/auth_tests.
Поиск неисправностей¶
Тестовый набор зависает или показывает сбои в ветке main¶
Убедитесь, что у вас установлена последняя версия поддерживаемой версии Python, поскольку в более ранних версиях часто встречаются ошибки, которые могут привести к сбою или зависанию набора тестов.
В macOS (High Sierra и более новых версиях) вы можете увидеть это сообщение logged, после чего тесты зависают:
objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.
Чтобы избежать этого, установите переменную окружения OBJC_DISABLE_INITIALIZE_FORK_SAFETY, например:
$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py
Или добавьте export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES в файл запуска вашей оболочки (например, ~/.profile).
Множество неудачных тестов с UnicodeEncodeError¶
Если пакет locales не установлен, некоторые тесты завершатся ошибкой UnicodeEncodeError.
Вы можете решить это в системах на базе Debian, например, выполнив:
$ apt-get install locales
$ dpkg-reconfigure locales
Эту проблему можно решить для систем macOS, настроив локаль вашей оболочки:
$ export LANG="en_US.UTF-8"
$ export LC_ALL="en_US.UTF-8"
Запустите команду locale, чтобы подтвердить изменение. При желании добавьте эти команды экспорта в файл запуска вашей оболочки (например, ~/.bashrc для Bash) чтобы избежать необходимости вводить их повторно.
Тесты, которые не работают только в сочетании¶
В случае, если тест проходит успешно при запуске изолированно, но не проходит в рамках всего набора, у нас есть некоторые инструменты, которые помогут проанализировать проблему.
Опция --bisect runtests.py запустит провальный тест, уменьшая вдвое набор тестов, с которым он запускается на каждой итерации, что часто позволяет идентифицировать небольшое количество тестов, которые могут быть связаны с провалом.
Например, предположим, что провальный тест, который работает сам по себе, это ModelTest.test_eq, тогда:
$ ./runtests.py --bisect basic.tests.ModelTest.test_eq
...\> runtests.py --bisect basic.tests.ModelTest.test_eq
попытается определить тест, который мешает заданному. Сначала тест запускается с первой половиной набора тестов. Если происходит сбой, первая половина набора тестов разделяется на две группы, и каждая группа затем запускается с указанным тестом. Если с первой половиной набора тестов не происходит сбоя, вторая половина набора тестов запускается с указанным тестом и разделяется соответствующим образом, как описано ранее. Процесс повторяется до тех пор, пока набор неудачных тестов не будет минимизирован.
Опция --pair запускает заданный тест вместе с каждым другим тестом из набора, позволяя вам проверить, есть ли у другого теста побочные эффекты, которые вызывают неудачу. Итак:
$ ./runtests.py --pair basic.tests.ModelTest.test_eq
...\> runtests.py --pair basic.tests.ModelTest.test_eq
свяжет test_eq с каждой тестовой меткой.
С --bisect и --pair, если вы уже подозреваете, какие случаи могут быть ответственны за сбой, вы можете ограничить тесты для перекрестногоанализа, указав дополнительные метки тестов после первой:
$ ./runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
...\> runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
Вы также можете попробовать запустить любой набор тестов в случайном или обратном порядке, используя опции --shuffle и --reverse. Это может помочь убедиться, что выполнение тестов в другом порядке не вызывает никаких проблем:
$ ./runtests.py basic --shuffle
$ ./runtests.py basic --reverse
...\> runtests.py basic --shuffle
...\> runtests.py basic --reverse
Просмотр SQL-запросов, выполняемых во время теста¶
Если вы хотите изучить SQL, выполняемый в неудачных тестах, вы можете включить SQL logging с помощью параметра --debug-sql. Если вы объедините это с --verbosity=2, все SQL-запросы будут выведены:
$ ./runtests.py basic --debug-sql
...\> runtests.py basic --debug-sql
Просмотр полной трассировки неудачного теста¶
По умолчанию тесты запускаются параллельно, по одному процессу на ядро. Однако, когда тесты запускаются параллельно, вы увидите только усеченную трассировку для любых сбоев теста. Вы можете настроить это поведение с помощью параметра --parallel:
$ ./runtests.py basic --parallel=1
...\> runtests.py basic --parallel=1
Вы также можете использовать переменную окружения DJANGO_TEST_PROCESSES для этой цели.
Советы по написанию тестов¶
Изоляция регистрации моделей¶
Чтобы избежать заполнения глобального реестра apps и предотвратить ненужное создание таблиц, модели, определенные в тестовом методе, должны быть привязаны к временному экземпляру Apps. Для этого используйте декоратор isolate_apps():
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps("app_label")
def test_model_definition(self):
class TestModel(models.Model):
pass
...
Настройка app_label
Моделям, определенным в тестовом методе без явного app_label автоматически назначается метка приложения, в котором находится их класс тестов.
Чтобы убедиться, что модели, определенные в контексте экземпляров isolate_apps(), установлены правильно, вам необходимо передать набор целевых app_label в качестве аргументов:
tests/app_label/tests.py¶from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps("app_label", "other_app_label")
def test_model_definition(self):
# This model automatically receives app_label='app_label'
class TestModel(models.Model):
pass
class OtherAppModel(models.Model):
class Meta:
app_label = "other_app_label"
...