Юнит тесты¶
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 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, 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
flake8
docs
isort>=5.1.0
...\> tox -l
py3
flake8
docs
isort>=5.1.0
Тестирование для других версий Python и бэкэндов баз данных¶
In addition to the default environments, tox supports running unit tests
for other versions of Python and other database backends. Since Django’s test
suite doesn’t bundle a settings file for database backends other than SQLite,
however, you must create and provide your own test settings. For example, to run the tests on Python 3.7
using PostgreSQL:
$ tox -e py37-postgres -- --settings=my_postgres_settings
...\> tox -e py37-postgres -- --settings=my_postgres_settings
This command sets up a Python 3.7 virtual environment, installs Django’s
test suite dependencies (including those for PostgreSQL), and calls
runtests.py with the supplied arguments (in this case,
--settings=my_postgres_settings).
В оставшейся части этой документации показаны команды для запуска тестов без tox, однако любой параметр, переданный в runtests.py, также может быть передан в tox, если добавить к списку аргументов префикс --, как указано выше.
Tox also respects the DJANGO_SETTINGS_MODULE environment variable, if
set. For example, the following is equivalent to the command above:
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py35-postgres
Пользователям Windows следует использовать:
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py35-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¶
The included settings module (tests/test_sqlite.py) allows you to run the
test suite using SQLite. If you want to run the tests using a different
database, you’ll need to define your own settings file. Some tests, such as
those for contrib.postgres, are specific to a particular database backend
and will be skipped if run with a different backend.
Чтобы запустить тесты с разными настройками, убедитесь, что модуль находится в вашей 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¶
Some tests require Selenium and a Web browser. To run these tests, you must
install the selenium package and run the tests with the
--selenium=<BROWSERS> option. For example, if you have Firefox and Google
Chrome installed:
$ ./runtests.py --selenium=firefox,chrome
...\> runtests.py --selenium=firefox,chrome
Список доступных браузеров см. в пакете selenium.webdriver.
Указание --selenium автоматически устанавливает --tags=selenium для запуска только тестов, которым требуется Selenium.
Некоторые браузеры (например, Chrome или Firefox) поддерживают headless-тестирование, которое может быть быстрее и стабильнее. Добавьте опцию --headless, чтобы включить этот режим.
Выполнение всех тестов¶
Если вы хотите запустить полный набор тестов, вам нужно установить ряд зависимостей:
argon2-cffi 16.1.0+
asgiref 3.2.10+ (required)
jinja2 2.7+
Pillow 6.2.0+
pytz (required)
memcached, plus a supported Python binding
sqlparse 0.2.2+ (required)
tblib 1.5.0+
You can find these dependencies in pip requirements files inside the
tests/requirements directory of the Django source tree and install them
like so:
$ 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.
If you want to test the memcached cache backend, you’ll also need to define
a CACHES setting that points at your memcached instance.
To run the GeoDjango tests, you will need to setup a spatial database and install the Geospatial libraries.
Каждая из этих зависимостей является необязательной. Если вы пропустите какую-либо из них, соответствующие тесты будут пропущены.
To run some of the autoreload tests, you’ll need to install the Watchman service.
Покрытие кода¶
Участникам рекомендуется запустить покрытие в наборе тестов, чтобы определить области, требующие дополнительных тестов. Установка и использование инструмента покрытия описаны в testing code coverage.
Coverage should be run in a single process to obtain accurate statistics. To run coverage on the Django test suite using the standard test settings:
$ coverage run ./runtests.py --settings=test_sqlite --parallel=1
...\> coverage run runtests.py --settings=test_sqlite --parallel=1
After running coverage, generate the html report by running:
$ coverage html
...\> coverage html
При запуске покрытия для тестов Django, файл настроек .coveragerc определяет coverage_html как выходной каталог для отчета, а также исключает несколько каталогов, не имеющих отношения к результатам (тестовый код или код, включенный в Django).
Приложения contrib¶
Tests for contrib apps can be found in the tests/ directory, typically
under <app_name>_tests. For example, tests for contrib.auth are located
in tests/auth_tests.
Поиск неисправностей¶
Test suite hangs or shows failures on master branch¶
Убедитесь, что у вас установлена последняя версия поддерживаемой версии Python, поскольку в более ранних версиях часто встречаются ошибки, которые могут привести к сбою или зависанию набора тестов.
On macOS (High Sierra and newer versions), you might see this message logged, after which the tests hang:
objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.
To avoid this set a OBJC_DISABLE_INITIALIZE_FORK_SAFETY environment
variable, for example:
$ 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
You can also try running any set of tests in reverse using the --reverse
option in order to verify that executing tests in a different order does not
cause any trouble:
$ ./runtests.py basic --reverse
...\> 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 для этой цели.
Советы по написанию тестов¶
Изоляция регистрации моделей¶
To avoid polluting the global apps registry and prevent
unnecessary table creation, models defined in a test method should be bound to
a temporary Apps instance:
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)¶
Since this pattern involves a lot of boilerplate, Django provides the
isolate_apps() decorator. It’s used like this:
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 в качестве аргументов:
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'
...
The decorator can also be applied to classes:
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
...
The temporary Apps instance used to isolate model registration can be
retrieved as an attribute when used as a class decorator by using the
attr_name parameter:
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)
Or as an argument on the test method when used as a method decorator by using
the kwarg_name parameter:
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)