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

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 - это инструмент для запуска тестов в различных виртуальных средах. Django включает базовый tox.ini, который автоматизирует некоторые проверки, которые наш сервер сборки выполняет при запросах на поставку. Чтобы запустить модульные тесты и другие проверки (такие как import sorting, documentation spelling checker и code formatting), установите и запустите команду tox из любого места в дереве исходников Django:

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

По умолчанию tox запускает набор тестов с прилагаемым файлом настроек тестов для SQLite, flake8, isort, и проверку орфографии документации. В дополнение к системным зависимостям, отмеченным в других местах этой документации, команда python3 должна быть в вашем пути и связана с соответствующей версией Python. Список окружений по умолчанию можно увидеть следующим образом:

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

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

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

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

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

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

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

$ 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 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, специфичны для определенного бэкенда базы данных и будут пропущены при запуске с другим бэкендом.

Чтобы запустить тесты с разными настройками, убедитесь, что модуль находится на вашем 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, вам также нужно определить параметр CACHES, который указывает на ваш экземпляр memcached.

Чтобы запустить тесты GeoDjango, вам нужно setup 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.

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

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

Убедитесь, что у вас есть последний точечный релиз 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

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

$ ./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.

Советы по написанию тестов

Изолирующая регистрация модели

Чтобы избежать загрязнения глобального 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)
Вернуться на верх