Дополнительные темы тестирования
Фабрика запросов
-
class RequestFactory[исходный код]
Фабрика RequestFactory имеет тот же API, что и клиент для тестирования. Однако, вместо имитации поведения браузера, RequestFactory предоставляет способ создания экземпляра запроса, который может быть использован в качестве первого аргумента для любого представления. Это означает, что вы можете протестировать функцию представления так же, как и любую другую функцию — как «черный ящик» с точно известными входными данными, проверяя конкретные выходные данные.
API для RequestFactory является слегка ограниченным подмножеством API клиента для тестирования:
- Он имеет доступ только к HTTP-методам
get(),post(),put(),delete(),head(),options()иtrace(). - Эти методы принимают все те же аргументы, кроме
follow. Поскольку это всего лишь фабрика для создания запросов, вам необходимо самостоятельно обработать ответ. - Он не поддерживает middleware. Атрибуты сессии и аутентификации должны быть предоставлены самим тестом, если они необходимы для корректной работы представления.
Пример
Следующий пример — простой модульный тест с использованием фабрики запросов:
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.
Тесты и несколько баз данных
Тестирование конфигураций primary/replica
Если вы тестируете конфигурацию с несколькими базами данных с репликацией primary/replica (иногда называемой master/slave в некоторых базах данных), эта стратегия создания тестовых баз данных создаёт проблему. Когда создаются тестовые базы данных, репликация отсутствует, и поэтому данные, созданные на primary, не будут отображаться на replica.
Для компенсации 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 test runner для запуска собственного набора тестов и, таким образом, воспользоваться инфраструктурой тестирования 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 test runner. Вы можете добавить опции командной строки для управления подробностью, передачи конкретных тегов тестов для выполнения и т.д.
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могла использовать эти аргументы.
Атрибуты
-
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] -
Выполняет системные проверки.
-
DiscoverRunner.run_suite(suite, **kwargs)[source] -
Выполняет набор тестов.
Возвращает результат, полученный при выполнении набора тестов.
-
DiscoverRunner.get_test_runner_kwargs()[source] -
Возвращает ключевые параметры для создания
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обновляется до ее значения.
-
teardown_test_environment()[source] -
Выполняет глобальное завершение после запуска тестов, например, удаляет средства отслеживания из системы шаблонов и восстанавливает обычные сервисы отправки электронных писем.
-
setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs)[source] -
Создаёт тестовые базы данных.
Возвращает структуру данных, которая предоставляет достаточно подробной информации для отмены изменений, которые были внесены. Эти данные будут предоставлены функции
teardown_databases()по окончании тестирования.
-
teardown_databases(old_config, parallel=0, keepdb=False)[source] -
Удаляет тестовые базы данных, восстанавливая исходное состояние перед тестом.
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.1/topics/testing/advanced/