Дополнительные темы тестирования
Фабрика запросов
-
class RequestFactory[исходный код]
Фабрика запросов RequestFactory использует тот же API, что и клиент для тестирования. Однако вместо имитации поведения браузера, RequestFactory предоставляет способ создания экземпляра запроса, который можно использовать в качестве первого аргумента любого представления. Это означает, что вы можете тестировать функцию представления так же, как и любую другую функцию — как «черный ящик», с точно известными входными данными, проверяя определённые выходные данные.
API фабрики запросов RequestFactory представляет собой немного ограниченный подмножество API клиента для тестирования:
- Он имеет доступ только к HTTP-методам
get(),post(),put(),delete(),head(),options()иtrace(). - Эти методы принимают все те же аргументы, кроме
follow. Поскольку это просто фабрика для создания запросов, обработка ответа зависит от вас. - Он не поддерживает промежуточное ПО. Атрибуты сеанса и аутентификации должны быть предоставлены самим тестом, если они необходимы для корректной работы представления.
Пример
Следующий пример — простой модульный тест с использованием фабрики запросов:
from django.contrib.auth.models import AnonymousUser, User
from django.test import RequestFactory, TestCase
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob@…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
Тесты и несколько имен хостов
Настройка ALLOWED_HOSTS проверяется при запуске тестов. Это позволяет клиенту тестов различать внутренние и внешние URL-адреса.
Проекты, которые поддерживают многопользовательский доступ или каким-либо образом изменяют бизнес-логику на основе хоста запроса и используют пользовательские имена хостов в тестах, должны включать эти хосты в ALLOWED_HOSTS.
Первый и самый простой способ сделать это — добавить хосты в файл настроек. Например, набор тестов для docs.djangoproject.com включает следующее:
from django.test import TestCase
class SearchFormTestCase(TestCase):
def test_empty_get(self):
response = self.client.get('/en/dev/search/', HTTP_HOST='docs.djangoproject.dev:8000')
self.assertEqual(response.status_code, 200)
а файл настроек содержит список доменов, поддерживаемых проектом:
ALLOWED_HOSTS = [
'www.djangoproject.dev',
'docs.djangoproject.dev',
...
]
Другой вариант — добавить необходимые хосты в ALLOWED_HOSTS с помощью override_settings() или modify_settings(). Этот вариант может быть предпочтительнее для автономных приложений, которые не могут упаковать свой собственный файл настроек, или для проектов, где список доменов не является статическим (например, поддомены для многопользовательского доступа). Например, вы можете написать тест для домена http://otherserver/ следующим образом:
from django.test import TestCase, override_settings
class MultiDomainTestCase(TestCase):
@override_settings(ALLOWED_HOSTS=['otherserver'])
def test_other_domain(self):
response = self.client.get('http://otherserver/foo/bar/')
Отключение проверки ALLOWED_HOSTS (ALLOWED_HOSTS = ['*']) при запуске тестов предотвращает вывод полезного сообщения об ошибке клиентом тестов, если вы перенаправляетесь на внешний URL-адрес.
В более старых версиях ALLOWED_HOSTS не проверялся во время тестирования, поэтому эти приёмы не были необходимы.
Тесты и несколько баз данных
Тестирование конфигураций мастер/раб
Если вы тестируете конфигурацию с несколькими базами данных с репликацией мастер/раб (в некоторых базах данных это называется репликацией мастер/раб), эта стратегия создания тестовых баз данных создаёт проблему. Когда создаются тестовые базы данных, репликации не будет, и в результате данные, созданные на главном сервере, не будут видны на рабе.
Для компенсации этого Django позволяет определить, что база данных является тестовым зеркалом. Рассмотрим следующий (упрощённый) пример конфигурации базы данных:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myproject',
'HOST': 'dbprimary',
# ... plus some other settings
},
'replica': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myproject',
'HOST': 'dbreplica',
'TEST': {
'MIRROR': 'default',
},
# ... plus some other settings
}
}
В этой настройке у нас есть два сервера баз данных: dbprimary, описанный псевдонимом базы данных default, и dbreplica под псевдонимом replica. Как можно предположить, dbreplica был настроен администратором базы данных как чтец-реплика dbprimary, поэтому в обычной деятельности любые записи в default будут отображаться в replica.
Если Django создавал две независимые тестовые базы данных, это нарушило бы любые тесты, которые ожидали, что репликация будет происходить. Однако база данных replica была настроена как тестовое зеркало (с помощью настройки теста MIRROR), указывающее, что во время тестирования replica должна рассматриваться как зеркало default.
Когда тестовая среда настроена, тестовая версия replica не будет создана. Вместо этого подключение к replica будет перенаправлено на default. В результате записи в default появятся в replica, но это не будет репликацией, а фактически одна база данных.
Управление порядком создания тестовых баз данных
По умолчанию Django предполагает, что все базы данных зависят от базы данных default и поэтому всегда создаёт базу данных default первой. Однако порядок создания других баз данных в вашей тестовой настройке не гарантируется.
Если ваша конфигурация базы данных требует определённого порядка создания, вы можете указать существующие зависимости, используя настройку теста DEPENDENCIES. Рассмотрим следующий (упрощённый) пример конфигурации базы данных:
DATABASES = {
'default': {
# ... db settings
'TEST': {
'DEPENDENCIES': ['diamonds'],
},
},
'diamonds': {
# ... db settings
'TEST': {
'DEPENDENCIES': [],
},
},
'clubs': {
# ... db settings
'TEST': {
'DEPENDENCIES': ['diamonds'],
},
},
'spades': {
# ... db settings
'TEST': {
'DEPENDENCIES': ['diamonds', 'hearts'],
},
},
'hearts': {
# ... db settings
'TEST': {
'DEPENDENCIES': ['diamonds', 'clubs'],
},
}
}
В этой конфигурации база данных diamonds будет создана первой, так как это единственный псевдоним базы данных без зависимостей. Затем будут созданы псевдонимы default и clubs (хотя порядок их создания не гарантируется), затем hearts, и, наконец, spades.
Если в определении DEPENDENCIES есть циклические зависимости, будет поднята ошибка ImproperlyConfigured.
Дополнительные возможности TransactionTestCase
-
TransactionTestCase.available_apps -
Предупреждение
Этот атрибут — это API частного использования. Он может быть изменён или удалён без периода устаревания, например, для адаптации к изменениям в загрузке приложений.
Он используется для оптимизации собственного набора тестов Django, который содержит сотни моделей, но без связей между моделями разных приложений.
По умолчанию
available_appsустановлено вNone. После каждого теста Django вызываетflushдля сброса состояния базы данных. Это очищает все таблицы и генерирует сигналpost_migrate, который повторно создаёт один тип содержимого и три разрешения для каждой модели. Эта операция становится дорогостоящей пропорционально количеству моделей.Установка
available_appsв список приложений сообщает Django, что доступны только модели из этих приложений. ПоведениеTransactionTestCaseизменяется следующим образом:- Сигнал
post_migrateзапускается перед каждым тестом для создания типов содержимого и разрешений для каждой модели в доступных приложениях, если они отсутствуют. - После каждого теста Django очищает только таблицы, соответствующие моделям в доступных приложениях. Однако на уровне базы данных обрезание может распространиться на связанные модели в недоступных приложениях. Кроме того, сигнал
post_migrateне генерируется; он будет сгенерирован следующимTransactionTestCase, после выбора правильного набора приложений.
Поскольку база данных не полностью очищается, если тест создаёт экземпляры моделей, не включённых в
available_apps, они останутся и могут привести к тому, что неуместные тесты провалятся. Будьте осторожны с тестами, которые используют сеансы; стандартный движок сеансов хранит их в базе данных.Поскольку сигнал
post_migrateне отправляется после очистки базы данных, её состояние послеTransactionTestCaseне такое, как послеTestCase: отсутствуют строки, созданные обработчиками сигналаpost_migrate. Учитывая порядок выполнения тестов, это не проблема, если всеTransactionTestCaseв данном наборе тестов объявляютavailable_apps, или если ни один из них не объявляет.available_appsявляется обязательным в собственном наборе тестов Django. - Сигнал
-
TransactionTestCase.reset_sequences -
Установка
reset_sequences = TrueвTransactionTestCaseгарантирует, что последовательности всегда будут сброшены перед запуском теста:class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase): reset_sequences = True def test_animal_pk(self): lion = Animal.objects.create(name="lion", sound="roar") # lion.pk is guaranteed to always be 1 self.assertEqual(lion.pk, 1)Если вы не явно не тестируете номера последовательности первичных ключей, рекомендуется не жестко задавать значения первичных ключей в тестах.
Использование
reset_sequences = Trueзамедлит тест, поскольку сброс первичного ключа — это относительно дорогостоящая операция в базе данных.
Использование Django-тестового исполнителя для тестирования повторно используемых приложений
Если вы пишете повторно используемое приложение, вы можете использовать Django-тестовый исполнитель для выполнения собственного набора тестов и тем самым воспользоваться инфраструктурой тестирования Django.
Обычной практикой является наличие каталога tests рядом с кодом приложения со следующей структурой:
runtests.py
polls/
__init__.py
models.py
...
tests/
__init__.py
models.py
test_settings.py
tests.py
Давайте посмотрим внутрь нескольких из этих файлов:
#!/usr/bin/env python
import os
import sys
import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))
Это скрипт, который вызывается для запуска набора тестов. Он настраивает среду Django, создает тестовую базу данных и запускает тесты.
Для ясности, этот пример содержит только минимум, необходимый для использования Django-тестового исполнителя. Вы можете добавить параметры командной строки для управления подробностью, передачи конкретных тегов для запуска и т. д.
SECRET_KEY = 'fake-key'
INSTALLED_APPS = [
"tests",
]
Этот файл содержит настройки Django, необходимые для запуска тестов вашего приложения.
Опять же, это минимальный пример; ваши тесты могут потребовать дополнительных настроек для запуска.
Поскольку пакет tests включен в INSTALLED_APPS при запуске тестов, вы можете определять тестовые модели только в его файле models.py.
Использование различных фреймворков для тестирования
Очевидно, что unittest не единственный фреймворк для тестирования Python. Хотя Django не предоставляет явной поддержки альтернативных фреймворков, он предоставляет способ вызова тестов, созданных для альтернативного фреймворка, как если бы они были обычными Django-тестами.
При запуске ./manage.py test, Django обращается к настройке TEST_RUNNER, чтобы определить, что делать. По умолчанию TEST_RUNNER указывает на 'django.test.runner.DiscoverRunner'. Этот класс определяет стандартное поведение Django-тестового исполнителя. Это поведение включает в себя:
- Выполнение глобальной подготовки перед запуском тестов.
- Поиск тестов в любом файле ниже текущей директории, имя которого соответствует шаблону
test*.py. - Создание тестовых баз данных.
- Выполнение
migrateдля установки моделей и начальных данных в тестовые базы данных. - Выполнение проверок системы.
- Выполнение найденных тестов.
- Удаление тестовых баз данных.
- Выполнение глобальной подготовки после завершения тестов.
Было добавлено выполнение проверок системы.
Если вы определите свой собственный класс тестового исполнителя и укажете TEST_RUNNER на этот класс, Django будет запускать ваш тестовый исполнитель всякий раз, когда вы запускаете ./manage.py test. Таким образом, можно использовать любой фреймворк для тестирования, который можно выполнить из кода Python, или изменить процесс выполнения Django-тестов для удовлетворения любых ваших потребностей в тестировании.
Определение тестового исполнителя
Тестовый исполнитель — это класс, определяющий метод run_tests(). Django поставляется с классом DiscoverRunner, который определяет стандартное поведение Django-тестового исполнителя. Этот класс определяет точку входа run_tests(), а также набор других методов, которые используются run_tests() для настройки, выполнения и завершения набора тестов.
-
class DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, **kwargs)[source] -
DiscoverRunnerбудет искать тесты во всех файлах, соответствующих шаблонуpattern.top_levelможет быть использован для указания директории, содержащей ваши главные Python-модули. Обычно Django может определить это автоматически, поэтому нет необходимости указывать этот параметр. Если он указан, он, как правило, должен быть директорией, содержащей ваш файлmanage.py.verbosityопределяет количество уведомлений и отладочной информации, которые будут выведены в консоль;0— отсутствие вывода,1— обычный вывод, а2— подробный вывод.Если
interactiveравноTrue, набор тестов может запросить у пользователя инструкции при выполнении набора тестов. Пример такого поведения — запрос разрешения на удаление существующей тестовой базы данных. ЕслиinteractiveравноFalse, набор тестов должен быть способен выполняться без ручного вмешательства.Если
failfastравноTrue, набор тестов остановит выполнение после обнаружения первой ошибки.Если
keepdbравноTrue, набор тестов будет использовать существующую базу данных или создать её, если необходимо. ЕслиFalse, будет создана новая база данных, и пользователя попросят удалить существующую, если она есть.Если
reverseравноTrue, тестовые кейсы будут выполняться в обратном порядке. Это может быть полезно для отладки тестов, которые не изолированы должным образом и имеют побочные эффекты. Группировка по классу теста сохраняется при использовании этого параметра.debug_modeопределяет, каким значением должно быть установленоDEBUGперед запуском тестов.Если
debug_sqlравноTrue, тестовые кейсы с ошибками будут выводить SQL-запросы, записанные в журнал django.db.backends, а также трассировку. Еслиverbosityравно2, запросы во всех тестах выводятся.Django может время от времени расширять возможности тестового исполнителя, добавляя новые аргументы. Объявление
**kwargsпозволяет такое расширение. Если вы создаете подклассDiscoverRunnerили пишете свой собственный тестовый исполнитель, убедитесь, что он принимает**kwargs.Ваш тестовый исполнитель также может определять дополнительные параметры командной строки. Создайте или переопределите метод класса
add_arguments(cls, parser)и добавьте пользовательские аргументы, вызвавparser.add_argument()внутри метода, чтобы командаtestмогла использовать эти аргументы.Новое в Django 1.11:Добавлен ключевой аргумент
debug_mode.
Атрибуты
-
DiscoverRunner.test_suite -
Класс, используемый для построения набора тестов. По умолчанию он установлен в
unittest.TestSuite. Это можно переопределить, если вы хотите реализовать другую логику для сбора тестов.
-
DiscoverRunner.test_runner -
Это класс низкоуровневого тестового исполнителя, используемого для выполнения отдельных тестов и форматирования результатов. По умолчанию он установлен в
unittest.TextTestRunner. Несмотря на неудачное сходство в обозначениях, это не тот же тип класса, что иDiscoverRunner, который охватывает более широкий круг задач. Вы можете переопределить этот атрибут, чтобы изменить способ выполнения и отчетности тестов.
-
DiscoverRunner.test_loader -
Это класс, загружающий тесты, независимо от того, являются ли они TestCases, модулями или иным образом, и объединяющий их в наборы тестов для выполнения исполнителем. По умолчанию он установлен в
unittest.defaultTestLoader. Вы можете переопределить этот атрибут, если ваши тесты будут загружаться необычными способами.
Методы
-
DiscoverRunner.run_tests(test_labels, extra_tests=None, **kwargs)[source] -
Запустить набор тестов.
test_labelsпозволяет указать, какие тесты нужно запустить, и поддерживает несколько форматов (см.DiscoverRunner.build_suite()для списка поддерживаемых форматов).extra_tests— это список дополнительных экземпляровTestCaseдля добавления в набор, который выполняется тестовым исполнителем. Эти дополнительные тесты выполняются в дополнение к тем, которые обнаружены в модулях, перечисленных вtest_labels.Этот метод должен вернуть количество тестов, которые завершились неудачно.
-
classmethod DiscoverRunner.add_arguments(parser)[source] -
Переопределите этот метод класса для добавления пользовательских аргументов, принимаемых командой управления
test. См.argparse.ArgumentParser.add_argument()для получения подробной информации о добавлении аргументов в парсер.
-
DiscoverRunner.setup_test_environment(**kwargs)[source] -
Настраивает тестовую среду, вызывая
setup_test_environment()и устанавливаяDEBUGвself.debug_mode(по умолчаниюFalse).
-
DiscoverRunner.build_suite(test_labels, extra_tests=None, **kwargs)[source] -
Создаёт набор тестов, соответствующий предоставленным меткам тестов.
test_labels— это список строк, описывающих тесты, которые нужно запустить. Метка теста может иметь один из четырёх форматов:-
path.to.test_module.TestCase.test_method— Запуск отдельного метода теста в тестовом случае. -
path.to.test_module.TestCase— Запуск всех методов теста в тестовом случае. -
path.to.module— Поиск и запуск всех тестов в указанном пакете или модуле Python. -
path/to/directory— Поиск и запуск всех тестов в указанной директории.
Если
test_labelsимеет значениеNone, тестовый загрузчик будет искать тесты во всех файлах ниже текущей директории, имена которых соответствуют егоpattern(см. выше).extra_tests— список дополнительныхTestCaseэкземпляров для добавления в набор, который выполняется тестовым загрузчиком. Эти дополнительные тесты выполняются в дополнение к тем, которые обнаружены в модулях, перечисленных вtest_labels.Возвращает экземпляр
TestSuite, готовый к запуску. -
-
DiscoverRunner.setup_databases(**kwargs)[source] -
Создаёт тестовые базы данных, вызвав
setup_databases().
-
DiscoverRunner.run_checks()[source] -
Новое в Django 1.11.
Выполняет системные проверки.
-
DiscoverRunner.run_suite(suite, **kwargs)[source] -
Выполняет набор тестов.
Возвращает результат, полученный при выполнении набора тестов.
-
DiscoverRunner.get_test_runner_kwargs()[source] -
Новое в Django 1.11.
Возвращает ключевые параметры для создания экземпляра
DiscoverRunner.test_runner.
-
DiscoverRunner.teardown_databases(old_config, **kwargs)[source] -
Удаляет тестовые базы данных, восстанавливая условия до теста, вызвав
teardown_databases().
-
DiscoverRunner.teardown_test_environment(**kwargs)[source] -
Восстанавливает среду до начала теста.
-
DiscoverRunner.suite_result(suite, result, **kwargs)[source] -
Вычисляет и возвращает код возврата, основанный на наборе тестов и результате выполнения этого набора тестов.
Утилиты тестирования
django.test.utils
Для помощи в создании собственного тестового загрузчика Django предоставляет ряд утилит в модуле django.test.utils.
-
setup_test_environment(debug=None)[source] -
Выполняет глобальную настройку перед запуском тестов, например, устанавливает инструменты для системы рендеринга шаблонов и настраивает буфер отправки фиктивных писем.
Если
debugнеNone, значение настройкиDEBUGобновляется.Изменено в Django 1.11:Добавлен аргумент
debug.
-
teardown_test_environment()[source] -
Выполняет глобальное завершение после выполнения тестов, например, удаляет инструменты из системы шаблонов и восстанавливает стандартные службы электронной почты.
-
setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs)[source] -
Новое в Django 1.11.
Создаёт тестовые базы данных.
Возвращает структуру данных, которая предоставляет достаточно информации, чтобы отменить изменения, которые были внесены. Эти данные будут предоставлены функции
teardown_databases()в конце тестирования.
-
teardown_databases(old_config, parallel=0, keepdb=False)[source] -
Новое в Django 1.11.
Удаляет тестовые базы данных, восстанавливая условия до начала теста.
old_config— это структура данных, определяющая изменения в конфигурации базы данных, которые нужно отменить. Это возвращаемое значение функцииsetup_databases().
django.db.connection.creation
Модуль создания подключения к базе данных также предоставляет некоторые утилиты, которые могут быть полезны во время тестирования.
-
create_test_db(verbosity=1, autoclobber=False, serialize=True, keepdb=False) -
Создаёт новую тестовую базу данных и выполняет
migrateнад ней.verbosityимеет то же поведение, что и вrun_tests().autoclobberописывает поведение, которое произойдёт, если обнаружена база данных с тем же именем, что и тестовая база данных:- Если
autoclobberравноFalse, пользователя попросят подтвердить уничтожение существующей базы данных.sys.exitвызывается, если пользователь не одобрит. - Если autoclobber равно
True, база данных будет уничтожена без консультации пользователя.
serializeопределяет, сериализует ли Django базу данных в строку JSON в оперативной памяти перед запуском тестов (используется для восстановления состояния базы данных между тестами, если у вас нет транзакций). Вы можете установить это вFalseдля ускорения времени создания, если у вас нет тестовых классов с serialized_rollback=True.Если вы используете стандартный тестовый загрузчик, вы можете управлять этим параметром с помощью записи
SERIALIZEв словареTEST.keepdbопределяет, следует ли использовать существующую базу данных при выполнении тестов или создавать новую. ЕслиTrue, будет использоваться существующая база данных, или создаваться, если она отсутствует. ЕслиFalse, будет создана новая база данных, и пользователя попросят удалить существующую, если она присутствует.Возвращает имя созданной тестовой базы данных.
create_test_db()имеет побочный эффект изменения значенияNAMEвDATABASESдля соответствия имени тестовой базы данных. - Если
-
destroy_test_db(old_database_name, verbosity=1, keepdb=False) -
Удаляет базу данных, имя которой является значением
NAMEвDATABASES, и устанавливаетNAMEв значениеold_database_name.Аргумент
verbosityимеет то же поведение, что и дляDiscoverRunner.Если аргумент
keepdbравенTrue, соединение с базой данных будет закрыто, но база данных не будет удалена.
Интеграция с coverage.py
Покрытие кода описывает, сколько исходного кода было протестировано. Оно показывает, какие части вашего кода обрабатываются тестами, а какие нет. Это важная часть тестирования приложений, поэтому настоятельно рекомендуется проверить покрытие ваших тестов.
Django легко интегрируется с coverage.py, инструментом для измерения покрытия кода Python-программ. Сначала установите coverage.py. Далее, выполните следующее из папки вашего проекта, содержащей manage.py:
coverage run --source='.' manage.py test myapp
Это выполняет ваши тесты и собирает данные о покрытии исполняемых файлов в вашем проекте. Вы можете увидеть отчёт об этих данных, набрав следующую команду:
coverage report
Обратите внимание, что некоторые кода Django были выполнены во время запуска тестов, но они не перечислены здесь из-за флага source , переданного в предыдущую команду.
Для получения дополнительных вариантов, таких как аннотированные списки HTML, показывающие пропущенные строки, см. документацию coverage.py.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/testing/advanced/