Расширенные темы тестирования
Фабрика запросов
-
class RequestFactory[исходный код]
Класс RequestFactory разделяет тот же API, что и клиент для тестирования. Однако вместо имитации поведения браузера, RequestFactory предоставляет способ генерации экземпляра запроса, который может быть использован в качестве первого аргумента любого представления. Это означает, что вы можете протестировать функцию представления так же, как и любую другую функцию — как чёрного ящика с точно известными входными данными, проверяя определённые выходные данные.
API RequestFactory представляет собой немного ограниченный подмножество API клиента для тестирования:
- Он имеет доступ только к методам HTTP
get(),post(),put(),delete(),head(),options()иtrace(). - Эти методы принимают все те же аргументы, кроме
follows. Поскольку это просто фабрика для создания запросов, обработка ответа зависит от вас. - Он не поддерживает middleware. Атрибуты сессии и аутентификации должны быть предоставлены самим тестом, если они необходимы для правильной работы представления.
Пример
Следующий пример представляет собой простой юнит-тест с использованием фабрики запросов:
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
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)
Тестирование и многочисленные базы данных
Тестирование конфигураций 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 для выполнения собственного набора тестов, а также воспользоваться инфраструктурой тестирования 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_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_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в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] -
Создает тестовые базы данных.
Возвращает структуру данных, которая предоставляет достаточно подробностей, чтобы отменить изменения, которые были внесены. Эти данные будут переданы функции
teardown_databases()по завершении тестирования.
-
DiscoverRunner.run_suite(suite, **kwargs)[source] -
Выполняет набор тестов.
Возвращает результат, полученный при запуске набора тестов.
-
DiscoverRunner.teardown_databases(old_config, **kwargs)[source] -
Удаляет тестовые базы данных, восстанавливая условия до начала теста.
old_config— это структура данных, определяющая изменения в конфигурации базы данных, которые нужно отменить. Это возвращаемое значение методаsetup_databases().
-
DiscoverRunner.teardown_test_environment(**kwargs)[source] -
Восстанавливает среду до начала теста.
-
DiscoverRunner.suite_result(suite, result, **kwargs)[source] -
Вычисляет и возвращает код возврата на основе набора тестов и результата этих тестов.
Утилиты тестирования
django.test.utils
Для помощи в создании собственного запускателя тестов Django предоставляет ряд утилит в модуле django.test.utils.
-
setup_test_environment()[source] -
Выполняет глобальную настройку перед запуском тестов, например, установку инструментирования для системы рендеринга шаблонов и настройку буфера фиктивных писем.
-
teardown_test_environment()[source] -
Выполняет глобальную разборку после запуска тестов, например, удаление инструментирования из системы шаблонов и восстановление нормальной работы сервисов электронной почты.
Создание соединения с базой данных для тестирования
Модуль создания соединения с базой данных также предоставляет некоторые утилиты, которые могут быть полезны при тестировании.
-
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/1.10/topics/testing/advanced/