Модульные тесты

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

Мы ценим любой и всякий вклад в тестовый пакет!

Все тесты Django используют инфраструктуру тестирования, поставляемую вместе с Django для тестирования приложений. См. Написание и выполнение тестов для объяснения того, как писать новые тесты.

Запуск модульных тестов

Быстрый старт

Во-первых, fork Django on GitHub.

Во-вторых, создайте и активируйте виртуальную среду. Если вы не знаете, как это сделать, прочитайте нашу статью contributing tutorial.

Затем клонируйте ваш форк, установите некоторые требования и запустите тесты:

$ 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 is a tool for running tests in different virtual environments. Django includes a basic tox.ini that automates some checks that our build server performs on pull requests. To run the unit tests and other checks (such as import sorting, the documentation spelling checker, and code formatting), install and run the tox command from any place in the Django source tree:

$ python -m pip install tox
$ tox
...\> py -m pip install tox
...\> tox

By default, tox runs the test suite with the bundled test settings file for SQLite, black, flake8, isort, and the documentation spelling checker. In addition to the system dependencies noted elsewhere in this documentation, the command python3 must be on your path and linked to the appropriate version of Python. A list of default environments can be seen as follows:

$ tox -l
py3
black
flake8>=3.7.0
docs
isort>=5.1.0
...\> tox -l
py3
black
flake8>=3.7.0
docs
isort>=5.1.0

Тестирование других версий Python и бэкендов баз данных

В дополнение к окружению по умолчанию, tox поддерживает запуск модульных тестов для других версий Python и других бэкендов баз данных. Поскольку набор тестов Django не содержит файла настроек для бэкендов баз данных, отличных от SQLite, вы должны create and provide your own test settings. Например, для запуска тестов на Python 3.9 с использованием PostgreSQL:

$ tox -e py39-postgres -- --settings=my_postgres_settings
...\> tox -e py39-postgres -- --settings=my_postgres_settings

Эта команда устанавливает виртуальную среду Python 3.9, устанавливает зависимости тестового пакета Django (включая зависимости для PostgreSQL) и вызывает runtests.py с заданными аргументами (в данном случае --settings=my_postgres_settings).

Остальная часть этой документации показывает команды для запуска тестов без tox, однако, любой параметр, переданный в runtests.py, также может быть передан в tox, префикснув список аргументов с --, как указано выше.

Tox также учитывает переменную окружения DJANGO_SETTINGS_MODULE, если она установлена. Например, следующая команда эквивалентна приведенной выше:

$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py39-postgres

Пользователи Windows должны использовать:

...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py39-postgres

Выполнение тестов JavaScript

Django включает набор JavaScript unit tests для функций в некоторых приложениях 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, чтобы включить этот режим.

Выполнение всех тестов

Если вы хотите запустить полный набор тестов, вам потребуется установить ряд зависимостей:

Вы можете найти эти зависимости в pip requirements files внутри каталога 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, вам нужно set up a spatial database and install the Geospatial libraries

Каждая из этих зависимостей является необязательной. Если какая-либо из них отсутствует, связанные с ней тесты будут пропущены.

Чтобы запустить некоторые тесты автозагрузки, вам нужно установить службу Watchman.

Покрытие кода

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

Для получения точной статистики покрытие следует запускать в одном процессе. Чтобы запустить покрытие на тестовом наборе Django, используя стандартные настройки тестов:

$ coverage run ./runtests.py --settings=test_sqlite --parallel=1
...\> coverage run runtests.py --settings=test_sqlite --parallel=1

После выполнения покрытия создайте html-отчет:

$ coverage html
...\> coverage html

При выполнении покрытия для тестов Django включенный файл настроек .coveragerc определяет coverage_html как выходной каталог для отчета, а также исключает несколько каталогов, не имеющих отношения к результатам (тестовый код или внешний код, включенный в Django).

Contrib-приложения

Тесты для приложений contrib можно найти в каталоге tests/, обычно в каталоге <app_name>_tests. Например, тесты для contrib.auth находятся в tests/auth_tests.

Устранение неполадок

Набор тестов зависает или показывает сбои на ветке main

Убедитесь, что у вас есть последний точечный релиз supported Python version, поскольку в более ранних версиях часто встречаются ошибки, которые могут привести к сбою или зависанию набора тестов.

На macOS (High Sierra и более новые версии) вы можете увидеть следующее сообщение в журнале, после чего тесты зависают:

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, если вы уже подозреваете, какие случаи могут быть причиной сбоя, вы можете ограничить тесты для перекрестного анализа с помощью specifying further test labels после первого:

$ ./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 экземпляру:

from django.apps.registry import Apps
from django.db import models
from django.test import SimpleTestCase

class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        test_apps = Apps(['app_label'])

        class TestModel(models.Model):
            class Meta:
                apps = test_apps
        ...
django.test.utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)

Поскольку этот шаблон включает в себя много шаблонов, Django предоставляет декоратор 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'
        ...

Декоратор также может быть применен к классам:

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps

@isolate_apps('app_label')
class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        class TestModel(models.Model):
            pass
        ...

Временный экземпляр Apps, используемый для изоляции регистрации модели, может быть получен как атрибут при использовании в качестве декоратора класса с помощью параметра attr_name:

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps

@isolate_apps('app_label', attr_name='apps')
class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        class TestModel(models.Model):
            pass
        self.assertIs(self.apps.get_model('app_label', 'TestModel'), TestModel)

Или в качестве аргумента метода проверки при использовании в качестве декоратора метода с помощью параметра kwarg_name:

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps

class TestModelDefinition(SimpleTestCase):
    @isolate_apps('app_label', kwarg_name='apps')
    def test_model_definition(self, apps):
        class TestModel(models.Model):
            pass
        self.assertIs(apps.get_model('app_label', 'TestModel'), TestModel)
Вернуться на верх