Инструменты тестирования
Django предоставляет небольшой набор инструментов, которые полезны при написании тестов.
Клиент тестирования
Клиент тестирования — это класс Python, который имитирует веб-браузер, позволяющий тестировать представления и взаимодействовать с приложением Django программно.
Некоторые операции, которые можно выполнить с помощью клиента тестирования:
- Имитировать запросы GET и POST к URL и наблюдать за ответом — от низкоуровневого HTTP (заголовки результата и коды состояния) до содержимого страницы.
- Проследить цепочку редиректов (если таковые имеются) и проверить URL и код состояния на каждом шаге.
- Проверить, что данный запрос отображается заданным шаблоном Django с контекстом шаблона, содержащим определенные значения.
Обратите внимание, что клиент тестирования не предназначен для замены Selenium или других фреймворков «в браузере». Клиент тестирования Django имеет другую направленность. Короче:
- Используйте клиент тестирования Django для проверки, что отображается правильный шаблон и шаблон получает правильные данные контекста.
- Используйте
RequestFactoryдля прямого тестирования функций представлений, минуя маршрутизацию и слои промежуточного программного обеспечения. - Используйте фреймворки в браузере, такие как Selenium, для тестирования отображенного HTML и поведения веб-страниц, а именно функциональности JavaScript. Django также предоставляет специальную поддержку для этих фреймворков; см. раздел о
LiveServerTestCaseдля получения дополнительных сведений.
Комплектный набор тестов должен использовать комбинацию всех этих типов тестов.
Обзор и быстрый пример
Для использования клиента тестирования инициализируйте django.test.Client и получайте веб-страницы:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post("/login/", {"username": "john", "password": "smith"})
>>> response.status_code
200
>>> response = c.get("/customer/details/")
>>> response.content
b'<!DOCTYPE html...'
Как показывает этот пример, вы можете инициализировать Client в сеансе интерактивного интерпретатора Python.
Обратите внимание на некоторые важные моменты работы клиента тестирования:
- Клиент тестирования не требует запуска веб-сервера. Фактически, он отлично работает без запуска веб-сервера! Это связано с тем, что он избегает накладных расходов HTTP и работает непосредственно с фреймворком Django. Это помогает ускорить выполнение модульных тестов.
-
При получении страниц не забудьте указать путь URL, а не весь домен. Например, это правильно:
>>> c.get("/login/")Это неправильно:
>>> c.get("https://www.example.com/login/")Клиент тестирования не может получать веб-страницы, которые не работают на базе вашего проекта Django. Если вам нужно получить другие веб-страницы, используйте модуль стандартной библиотеки Python, такой как
urllib. - Для разрешения URL клиент тестирования использует URLconf, указанный в настройке
ROOT_URLCONF. -
Хотя приведенный выше пример будет работать в интерактивном интерпретаторе Python, некоторые функциональные возможности клиента тестирования, в частности связанные с шаблонами, доступны только во время выполнения тестов.
Причина в том, что тестовый запуск Django выполняет некоторую «магию», чтобы определить, какой шаблон был загружен данным представлением. Эта «магия» (по сути, патчинг системы шаблонов Django в памяти) происходит только во время выполнения тестов.
-
По умолчанию клиент тестирования отключит любые проверки CSRF, выполняемые вашим сайтом.
Если по какой-либо причине вы хотите, чтобы клиент тестирования выполнял проверки CSRF, вы можете создать экземпляр клиента тестирования, который выполняет проверки CSRF. Для этого передайте аргумент
enforce_csrf_checksпри создании своего клиента:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Выполнение запросов
Используйте класс django.test.Client для выполнения запросов.
-
class Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults)[source]
-
Тестовый HTTP-клиент. Принимает несколько аргументов, которые могут настроить его поведение.
headersпозволяет указать значения по умолчанию для заголовков, которые будут отправляться с каждым запросом. Например, чтобы установить заголовокUser-Agent:client = Client(headers={"user-agent": "curl/7.79.1"})query_paramsпозволяет указать строку запроса по умолчанию, которая будет устанавливаться в каждом запросе.Произвольные ключевые аргументы в
**defaultsустанавливают переменные среды WSGI переменные окружения. Например, чтобы установить имя сценария:client = Client(SCRIPT_NAME="/app/")
Примечание
Ключевые аргументы, начинающиеся с префикса
HTTP_, устанавливаются как заголовки, но параметрheadersпредпочтительнее для читаемости.Значения из ключевых аргументов
headers,query_paramsиextra, переданных вget(),post()и т. д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.Аргумент
enforce_csrf_checksможет использоваться для проверки защиты от CSRF (см. выше).Аргумент
raise_request_exceptionпозволяет контролировать, должны ли исключения, возникающие во время запроса, также подниматься в тесте. По умолчаниюTrue.Аргумент
json_encoderпозволяет установить пользовательский кодировщик JSON для сериализации JSON, которая описана вpost().Изменено в Django 5.1:Добавлен аргумент
query_params.После получения экземпляра
Client, вы можете вызвать любой из следующих методов:-
get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос GET к указанному
pathи возвращает объектResponse, который документирован ниже.Ключевые пары в словаре
query_paramsиспользуются для установки строк запроса. Например:>>> c = Client() >>> c.get("/customers/details/", query_params={"name": "fred", "age": 7})…приведёт к выполнению запроса GET, эквивалентного:
/customers/details/?name=fred&age=7
Также возможно передать эти параметры в параметр
data. Однако,query_paramsпредпочтительнее, так как работает для любого HTTP-метода.Параметр
headersможно использовать для указания заголовков, которые должны быть отправлены в запросе. Например:>>> c = Client() >>> c.get( ... "/customers/details/", ... query_params={"name": "fred", "age": 7}, ... headers={"accept": "application/json"}, ... )…отправит HTTP-заголовок
HTTP_ACCEPTв представление деталей, что является хорошим способом проверки путей кода, использующих методdjango.http.HttpRequest.accepts().Произвольные ключевые аргументы устанавливают WSGI переменные окружения. Например, для установки имени сценария:
>>> c = Client() >>> c.get("/", SCRIPT_NAME="/app/")Если у вас уже есть аргументы GET в кодированном формате, вы можете использовать эту кодировку вместо аргумента data. Например, предыдущий запрос GET также можно отправить как:
>>> c = Client() >>> c.get("/customers/details/?name=fred&age=7")Если вы предоставите URL с закодированными данными GET и аргументом query_params или data, эти аргументы будут иметь приоритет.
Если вы установите
followвTrue, клиент будет следовать всем редиректам, и в объекте ответа будет установлен атрибутredirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.Если у вас был URL
/redirect_me/, который перенаправлял на/next/, который перенаправлял на/final/, вот что вы увидите:>>> response = c.get("/redirect_me/", follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]Если вы установите
secureвTrue, клиент будет эмулировать HTTPS-запрос.Изменено в Django 5.1:Добавлен аргумент
query_params.
-
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос POST к указанному
pathи возвращает объектResponse, который документирован ниже.Ключевые пары в словаре
dataиспользуются для отправки данных POST. Например:>>> c = Client() >>> c.post("/login/", {"name": "fred", "passwd": "secret"})…приведёт к выполнению запроса POST по этому URL:
/login/
…с этими данными POST:
name=fred&passwd=secret
Если вы укажете
content_typeкак application/json, тоdataбудет сериализован с помощьюjson.dumps(), если это словарь, список или кортеж. Сериализация выполняется с помощьюDjangoJSONEncoderпо умолчанию и может быть переопределена путём предоставления аргументаjson_encoderметодуClient. Эта сериализация также происходит для запросовput(),patch()иdelete().Если вы укажете любой другой
content_type(например, text/xml для XML-данных), содержимоеdataотправляется как есть в запросе POST, используяcontent_typeв HTTP-заголовкеContent-Type.Если вы не укажете значение для
content_type, значения вdataбудут переданы с типом контента multipart/form-data. В этом случае ключевые пары вdataбудут закодированы как multipart-сообщение и использованы для создания полезной нагрузки данных POST.Для отправки нескольких значений для одного ключа — например, для указания выбранных значений для поля
<select multiple>— предоставьте значения в виде списка или кортежа для требуемого ключа. Например, это значениеdataотправит три выбранных значения для поля с именемchoices:{"choices": ["a", "b", "d"]}Отправка файлов — это особый случай. Для отправки файла нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например, если у вашей формы есть поля
nameиattachment, последнее —FileField:>>> c = Client() >>> with open("wishlist.doc", "rb") as fp: ... c.post("/customers/wishes/", {"name": "fred", "attachment": fp}) ...Вы также можете предоставить любой объект типа «файл» (например,
StringIOилиBytesIO) как дескриптор файла. Если вы загружаете файл вImageField, объекту нужен атрибутname, который проходит валидаторvalidate_image_file_extension. Например:>>> from io import BytesIO >>> img = BytesIO( ... b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00" ... b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00" ... ) >>> img.name = "myimage.gif"
Обратите внимание, что если вы хотите использовать один и тот же дескриптор файла для нескольких вызовов
post(), вам нужно будет вручную сбрасывать указатель файла между запросами. Самый простой способ сделать это — вручную закрыть файл после того, как он был предоставлен методуpost(), как показано выше.Вы также должны убедиться, что файл открыт таким образом, чтобы данные могли быть считаны. Если ваш файл содержит двоичные данные, такие как изображение, это означает, что вам нужно открыть файл в режиме
rb(чтение в двоичном формате).Параметры
headers,query_paramsиextraработают так же, как и дляClient.get().Если URL, который вы запрашиваете с помощью POST, содержит закодированные параметры, эти параметры будут доступны в данных request.GET. Например, если вы выполните запрос:
>>> c.post( ... "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"} ... )…представление, обрабатывающее этот запрос, сможет получить имя пользователя и пароль из request.POST и определить, является ли пользователь посетителем из request.GET.
Если вы установите
followвTrue, клиент будет следовать всем редиректам, и в объекте ответа будет установлен атрибутredirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.Если вы установите
secureвTrue, клиент будет эмулировать HTTPS-запрос.Изменено в Django 5.1:Добавлен аргумент
query_params.
-
head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос HEAD к указанному
pathи возвращает объектResponse. Этот метод работает так же, какClient.get(), включая параметрыfollow,secure,headers,query_paramsиextra, за исключением того, что он не возвращает тело сообщения.Изменено в Django 5.1:Добавлен аргумент
query_params.
-
-
options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос OPTIONS к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Если указан параметр
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается в значениеcontent_type.Параметры
follow,secure,headers,query_paramsиextraдействуют так же, как и дляClient.get().Изменено в Django 5.1:Добавлен аргумент
query_params.
-
put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос PUT к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Если указан параметр
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается в значениеcontent_type.Параметры
follow,secure,headers,query_paramsиextraдействуют так же, как и дляClient.get().Изменено в Django 5.1:Добавлен аргумент
query_params.
-
patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос PATCH к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Параметры
follow,secure,headers,query_paramsиextraдействуют так же, как и дляClient.get().Изменено в Django 5.1:Добавлен аргумент
query_params.
-
delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос DELETE к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Если указан параметр
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается в значениеcontent_type.Параметры
follow,secure,headers,query_paramsиextraдействуют так же, как и дляClient.get().Изменено в Django 5.1:Добавлен аргумент
query_params.
-
trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source] -
Выполняет запрос TRACE к указанному
pathи возвращает объектResponse. Полезно для моделирования диагностических зондов.В отличие от других методов запроса,
dataне предоставляется как параметр ключевого слова для соответствия RFC 9110 Раздел 9.3.8, который предписывает, что запросы TRACE не должны иметь тело.Параметры
follow,secure,headers,query_paramsиextraдействуют так же, как и дляClient.get().Изменено в Django 5.1:Добавлен аргумент
query_params.
-
login(**credentials)
-
alogin(**credentials) -
Асинхронная версия:
alogin()Если ваш сайт использует систему аутентификации Django, вы можете использовать метод
login()тестового клиента для моделирования входа пользователя на сайт.После вызова этого метода, тестовый клиент будет иметь все необходимые куки и данные сессии для прохождения любых тестов, связанных с входом в систему.
Формат аргумента
credentialsзависит от используемого модуля аутентификации (настраивается вашим параметромAUTHENTICATION_BACKENDS). Если используется стандартный модуль аутентификации Django (ModelBackend),credentialsдолжен содержать имя пользователя и пароль в качестве ключевых аргументов:>>> c = Client() >>> c.login(username="fred", password="secret") # Now you can access a view that's only available to logged-in users.
Если используется другой модуль аутентификации, для него могут потребоваться другие данные. Требуются данные, необходимые методу
authenticate()вашего модуля.login()возвращаетTrue, если данные были приняты и вход в систему выполнен успешно.В заключение, вам необходимо создать учетные записи пользователей перед использованием этого метода. Как упоминалось выше, тестовый запуск выполняется с использованием тестовой базы данных, которая по умолчанию не содержит пользователей. Следовательно, учетные записи пользователей, действующие на вашем сайте в рабочей среде, не будут работать в тестовых условиях. Вам нужно создать пользователей в составе набора тестов — либо вручную (используя API модели Django), либо с помощью тестового фикстура. Помните, что если вы хотите, чтобы у вашего тестового пользователя был пароль, вы не можете установить пароль, задав атрибут password напрямую — вы должны использовать функцию
set_password()для хранения правильно закодированного пароля. Альтернативно, можно использовать вспомогательный методcreate_user()для создания нового пользователя с правильно закодированным паролем.
-
force_login(user, backend=None)
-
aforce_login(user, backend=None) -
Асинхронная версия:
aforce_login()Если ваш сайт использует систему аутентификации Django, вы можете использовать метод
force_login()для моделирования входа пользователя на сайт. Используйте этот метод вместоlogin()в тех случаях, когда тест требует входа пользователя, а подробности о том, как пользователь вошел в систему, не важны.В отличие от
login(), этот метод пропускает шаги аутентификации и проверки: неактивные пользователи (is_active=False) могут войти в систему, и данные пользователя не требуются.Атрибут пользователя
backendбудет установлен в значение аргументаbackend(который должен быть строкой с путём в Python), или вsettings.AUTHENTICATION_BACKENDS[0], если значение не указано. Функцияauthenticate(), вызываемаяlogin(), обычно анотирует пользователя таким образом.Этот метод быстрее, чем
login(), поскольку дорогостоящие алгоритмы хеширования паролей пропускаются. Также можно ускоритьlogin(), используя более слабый хешер во время тестирования.
-
logout()
-
alogout() -
Асинхронная версия:
alogout()Если ваш сайт использует систему аутентификации Django, метод
logout()может использоваться для моделирования выхода пользователя из вашей системы.После вызова этого метода, тестовый клиент будет иметь все куки и данные сессии, очищенные до стандартных значений. Последующие запросы будут выглядеть так, как будто они поступают от
AnonymousUser.
-
Ответы тестов
Методы get() и post() оба возвращают объект Response. Этот объект Response не является таким же, как объект HttpResponse, возвращаемый представлениями Django; объект ответа теста содержит некоторые дополнительные данные, полезные для кода теста для проверки.
Конкретно, объект Response имеет следующие атрибуты:
-
class Response -
-
client -
Клиент тестирования, который был использован для отправки запроса, который привёл к ответу.
-
content -
Тело ответа в виде байтовой строки. Это окончательное содержимое страницы, как отображено представлением, или любое сообщение об ошибке.
-
context -
Экземпляр шаблона
Context, который использовался для рендеринга шаблона, который произвёл содержимое ответа.Если рендер страницы использовал несколько шаблонов, то
contextбудет списком объектовContextв порядке, в котором они были рендерены.Независимо от количества шаблонов, используемых во время рендеринга, вы можете получить значения контекста, используя оператор
[]. Например, переменная контекстаnameможет быть получена следующим образом:>>> response = client.get("/foo/") >>> response.context["name"] 'Arthur'Вы не используете Django шаблоны?
Этот атрибут заполняется только при использовании бэкэнда
DjangoTemplates. Если вы используете другой движок шаблонов,context_dataможет быть подходящей альтернативой для ответов с этим атрибутом.
-
exc_info -
Кортеж из трёх значений, которые предоставляют информацию об обработанном исключении, если таковое имело место во время обработки представления.
Значениями являются (тип, значение, трассировка), такие же, как возвращает функция Python
sys.exc_info(). Их значения:- тип: тип исключения.
- значение: экземпляр исключения.
- трассировка: объект трассировки, который включает стек вызовов в момент, когда произошла ошибка.
Если исключений не было, то
exc_infoбудетNone.
-
json(**kwargs) -
Тело ответа, распарсенное как JSON. Дополнительные ключевые аргументы передаются функции
json.loads(). Например:>>> response = client.get("/foo/") >>> response.json()["name"] 'Arthur'Если заголовок
Content-Typeне"application/json", при попытке парсинга ответа будет поднято исключениеValueError.
-
request -
Данные запроса, которые спровоцировали ответ.
-
wsgi_request -
Экземпляр
WSGIRequest, сгенерированный обработчиком теста, который сгенерировал ответ.
-
status_code -
HTTP статус ответа, как целое число. Полный список кодов см. в реестре кодов статуса IANA.
-
templates -
Список экземпляров
Template, используемых для рендеринга окончательного содержимого в порядке их рендеринга. Для каждого шаблона в списке используйтеtemplate.name, чтобы получить имя файла шаблона, если шаблон был загружен из файла. (Имя — строка, например,'admin/index.html'.)Вы не используете Django шаблоны?
Этот атрибут заполняется только при использовании бэкэнда
DjangoTemplates. Если вы используете другой движок шаблонов,template_nameможет быть подходящей альтернативой, если вам нужно только имя шаблона, используемого для рендеринга.
-
resolver_match -
Экземпляр
ResolverMatchдля ответа. Вы можете использовать атрибутfunc, например, для проверки представления, которое обслужило ответ:# my_view here is a function based view. self.assertEqual(response.resolver_match.func, my_view) # Class-based views need to compare the view_class, as the # functions generated by as_view() won't be equal. self.assertIs(response.resolver_match.func.view_class, MyView)
Если указанный URL не найден, доступ к этому атрибуту вызовет исключение
Resolver404.
-
Как и в обычном ответе, к заголовкам можно получить доступ через HttpResponse.headers. Например, вы можете определить тип содержимого ответа, используя response.headers['Content-Type'].
Исключения
Если вы направляете клиента тестирования на представление, которое вызывает исключение, и Client.raise_request_exception равно True, это исключение будет видно в тесте. Тогда вы можете использовать стандартный блок try ... except или assertRaises() для проверки исключений.
Единственными исключениями, которые не видны клиенту тестирования, являются Http404, PermissionDenied, SystemExit и SuspiciousOperation. Django обрабатывает эти исключения внутренне и преобразует их в соответствующие коды HTTP-ответа. В этих случаях вы можете проверить response.status_code в вашем тесте.
Если Client.raise_request_exception равно False, клиент тестирования вернёт ответ 500, как и было бы возвращено браузеру. Ответ имеет атрибут exc_info для предоставления информации об обработанном исключении.
Состояние, сохраняющееся между запросами
Клиент тестирования имеет состояние. Если ответ возвращает cookie, то этот cookie будет сохранён в клиенте тестирования и отправлен со всеми последующими запросами get() и post().
Политики истечения срока действия этих cookies не соблюдаются. Если вы хотите, чтобы cookie истек, либо удалите его вручную, либо создайте новый экземпляр Client (что фактически удалит все cookies).
Клиент тестирования имеет атрибуты, которые хранят информацию о состоянии, сохраняющемся между запросами. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Client.cookies -
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. Для получения дополнительной информации см. документацию по модулюhttp.cookies.
-
Client.session -
Объект, подобный словарю, содержащий информацию о сессии. Для получения подробностей см. документацию по сессиям.
Чтобы изменить сессию и сохранить её, её нужно сначала сохранить в переменную (потому что новый
SessionStoreсоздаётся каждый раз при доступе к этому свойству):def test_something(self): session = self.client.session session["somekey"] = "test" session.save()
-
Client.asession() -
Это аналогично атрибуту
session, но работает в асинхронных контекстах.
Настройка языка
При тестировании приложений, поддерживающих международные и локальные настройки, вы можете установить язык для запроса тестового клиента. Способ зависит от того, включен ли LocaleMiddleware.
Если среда включена, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"})
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив Accept-Language HTTP-заголовок в запросе:
def test_language_using_header(self):
response = self.client.get("/", headers={"accept-language": "fr"})
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Примечание
При использовании этих методов убедитесь, что активный язык сброшен в конце каждого теста:
def tearDown(self):
translation.activate(settings.LANGUAGE_CODE)
Дополнительные сведения см. в Как Django определяет предпочтения языка.
Если среда не включена, активный язык можно установить, используя translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override("fr"):
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Явное задание активного языка.
Пример
Ниже приведен модульный тест с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get("/customer/details/")
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context["customers"]), 5)
См. также
Предоставленные классы тестовых случаев
Обычные классы тестов Python расширяют базовый класс unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Иерархия классов тестирования Django
Вы можете преобразовать обычный unittest.TestCase в любой из подклассов: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные функции тестов Python будут доступны, и они будут дополнены некоторыми полезными дополнениями, как описано в каждом разделе ниже.
SimpleTestCase
-
class SimpleTestCase[source]
Подкласс unittest.TestCase, добавляющий следующие возможности:
-
Некоторые полезные утверждения, такие как:
- Проверка, что вызываемый объект
raises a certain exception. - Проверка, что вызываемый объект
triggers a certain warning. - Тестирование поля формы
rendering and error treatment. - Тестирование
HTML responses for the presence/lack of a given fragment. - Проверка того, что шаблон
has/hasn't been used to generate a given response content. - Проверка, что два
URLsравны. - Проверка, что приложение выполняет перенаправление HTTP
redirect. - Надежное тестирование двух
HTML fragmentsна равенство/неравенство илиcontainment. - Надежное тестирование двух
XML fragmentsна равенство/неравенство. - Надежное тестирование двух
JSON fragmentsна равенство.
- Проверка, что вызываемый объект
- Возможность запуска тестов с измененными настройками.
- Использование
clientClient.
Если ваши тесты выполняют запросы к базе данных, используйте подклассы TransactionTestCase или TestCase.
-
SimpleTestCase.databases -
SimpleTestCaseпо умолчанию запрещает запросы к базе данных. Это помогает избежать выполнения запросов записи, которые повлияют на другие тесты, так как каждыйSimpleTestCaseтест не выполняется в транзакции. Если вас не беспокоит эта проблема, вы можете отключить это поведение, установивdatabasesатрибут класса на'__all__'в вашем тестовом классе.
Предупреждение
SimpleTestCase и его подклассы (например, TestCase, …) полагаются на setUpClass() и tearDownClass() для выполнения некоторых инициализаций на уровне класса (например, перезаписи настроек). Если вам нужно перезаписать эти методы, не забудьте вызвать реализацию super:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
Учитывайте поведение Python, если во время setUpClass() возникает исключение. В этом случае тесты в классе и tearDownClass() не выполняются. В случае с django.test.TestCase это приведет к утечке транзакции, созданной в super(), что приводит к различным симптомам, включая сбой сегментации на некоторых платформах (сообщено на macOS). Если вы хотите намеренно вызвать исключение, такое как unittest.SkipTest в setUpClass(), убедитесь, что вы это сделаете до вызова super(), чтобы избежать этого.
TransactionTestCase
-
class TransactionTestCase[source]
TransactionTestCase наследуется от SimpleTestCase для добавления некоторых функций, специфичных для базы данных:
- Сброс базы данных до известного состояния в конце каждого теста для облегчения тестирования и использования ORM.
- Базовых данных
fixtures. - Пропускание тестов в зависимости от функций бэкенда базы данных.
- Остальные специализированные методы
assert*.
Класс TestCase Django является более часто используемым подклассом TransactionTestCase, который использует возможности транзакций базы данных для ускорения процесса сброса базы данных до известного состояния в конце каждого теста. Однако следствием этого является то, что некоторые поведенческие особенности базы данных не могут быть протестированы внутри класса Django TestCase. Например, вы не можете проверить, что блок кода выполняется в транзакции, как требуется при использовании select_for_update(). В этих случаях вы должны использовать TransactionTestCase.
TransactionTestCase и TestCase идентичны, за исключением способа сброса базы данных до известного состояния и возможности для кода теста проверять эффекты commit и rollback:
TransactionTestCaseсбрасывает базу данных после выполнения теста, обнуляя все таблицы.TransactionTestCaseможет вызывать commit и rollback и наблюдать влияние этих вызовов на базу данных.TestCase, с другой стороны, не обнуляет таблицы после выполнения теста. Вместо этого он помещает код теста в транзакцию базы данных, которая отменяется в конце теста. Это гарантирует, что отмена в конце теста восстанавливает базу данных в исходное состояние.
Предупреждение
TestCase, работающий с базой данных, которая не поддерживает rollback (например, MySQL с движком хранилища MyISAM), и все экземпляры TransactionTestCase, откатываются в конце теста, удаляя все данные из тестовой базы данных.
Приложения не увидят перезагрузку своих данных; если вам нужна эта функциональность (например, сторонние приложения должны ее включить), вы можете установить serialized_rollback = True внутри тела TestCase.
TestCase
-
class TestCase[source]
Это наиболее распространённый класс для написания тестов в Django. Он наследуется от TransactionTestCase (и, следовательно, от SimpleTestCase). Если ваше приложение Django не использует базу данных, используйте SimpleTestCase.
Класс:
- Оборачивает тесты в два вложенных блока
atomic(): один для всего класса и один для каждого теста. Поэтому, если вы хотите протестировать определённое поведение транзакции базы данных, используйтеTransactionTestCase. - Проверяет откладываемые ограничения базы данных в конце каждого теста.
Он также предоставляет дополнительный метод:
-
classmethod TestCase.setUpTestData()[source] -
Указанный выше блок уровня класса
atomicпозволяет создавать начальные данные на уровне класса, один раз для всегоTestCase. Этот метод позволяет ускорить тесты по сравнению с использованиемsetUp().Например:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...Обратите внимание, что если тесты выполняются на базе данных без поддержки транзакций (например, MySQL с движком MyISAM),
setUpTestData()будет вызываться перед каждым тестом, что сводит на нет преимущества скорости.Объекты, присвоенные атрибутам класса в
setUpTestData(), должны поддерживать создание глубоких копий с помощьюcopy.deepcopy()для изоляции их от изменений, выполняемых методами каждого теста.
-
classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[source] -
Возвращает менеджер контекста, который захватывает
transaction.on_commit()обратные вызовы для данного подключения к базе данных. Он возвращает список, который содержит, по завершении контекста, захваченные функции обратного вызова. Из этого списка вы можете сделать утверждения о вызовах или вызвать их, чтобы вызвать их побочные эффекты, имитируя подтверждение.using— псевдоним подключения к базе данных для захвата обратных вызовов.Если
executeравенTrue, все обратные вызовы будут вызваны по завершении менеджера контекста, если не произошла ошибка. Это имитирует подтверждение после обернутого блока кода.Например:
from django.core import mail from django.test import TestCase class ContactTests(TestCase): def test_post(self): with self.captureOnCommitCallbacks(execute=True) as callbacks: response = self.client.post( "/contact/", {"message": "I like your site"}, ) self.assertEqual(response.status_code, 200) self.assertEqual(len(callbacks), 1) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, "Contact Form") self.assertEqual(mail.outbox[0].body, "I like your site")
LiveServerTestCase
-
class LiveServerTestCase[source]
LiveServerTestCase выполняет в основном то же, что и TransactionTestCase, с одной дополнительной функцией: запускает активный сервер Django на заднем плане при настройке и закрывает его при разборе. Это позволяет использовать автоматические клиентские тесты, отличные от Django dummy client, такие как, например, клиент Selenium, для выполнения серии функциональных тестов внутри браузера и имитации действий реального пользователя.
Активный сервер прослушивает localhost и привязывается к порту 0, который использует свободный порт, назначенный операционной системой. К URL-адресу сервера можно получить доступ с помощью self.live_server_url во время тестов.
Чтобы продемонстрировать использование LiveServerTestCase, напишем Selenium-тест. Прежде всего, вам необходимо установить пакет selenium:
$ python -m pip install "selenium >= 4.8.0"
...\> py -m pip install "selenium >= 4.8.0"
Затем добавьте тест на основе LiveServerTestCase в модуль тестов приложения (например: myapp/tests.py). Для этого примера предположим, что вы используете приложение staticfiles и хотите, чтобы статические файлы обслуживались во время выполнения тестов так же, как это происходит на этапе разработки с DEBUG=True, то есть без необходимости сбора их с помощью collectstatic. Мы будем использовать подкласс StaticLiveServerTestCase, который предоставляет эту функциональность. Замените её на django.test.LiveServerTestCase, если вам это не нужно.
Код этого теста может выглядеть следующим образом:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ["user-data.json"]
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get(f"{self.live_server_url}/login/")
username_input = self.selenium.find_element(By.NAME, "username")
username_input.send_keys("myuser")
password_input = self.selenium.find_element(By.NAME, "password")
password_input.send_keys("secret")
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
Наконец, вы можете запустить тест следующим образом:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
Этот пример автоматически откроет Firefox, перейдёт на страницу входа, введёт учетные данные и нажмёт кнопку «Войти». Selenium предлагает другие драйверы на случай, если у вас нет установленного Firefox или вы хотите использовать другой браузер. Приведенный выше пример — лишь малая часть того, что может делать Selenium-клиент; обратитесь к полной справке для получения дополнительных сведений.
Примечание
При использовании памяти SQLite для выполнения тестов одно и то же соединение с базой данных будет использоваться двумя потоками параллельно: потоком, в котором запущен активный сервер, и потоком, в котором выполняется тестовый случай. Важно предотвратить одновременные запросы к базе данных через это общее соединение двумя потоками, так как это иногда случайно приводит к провалу тестов. Таким образом, вам нужно убедиться, что оба потока не обращаются к базе данных одновременно. В частности, это означает, что в некоторых случаях (например, сразу после нажатия ссылки или отправки формы) вам, возможно, потребуется убедиться, что Selenium получает ответ и что следующая страница загружена перед продолжением дальнейшего выполнения теста. Сделайте это, например, заставив Selenium дождаться, пока тег HTML <body> не будет найден в ответе (требуется Selenium > 2.13):
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element(By.TAG_NAME, "body")
)
Сложность здесь в том, что в современных веб-приложениях, которые генерируют HTML динамически после того, как сервер сгенерировал начальный документ, нет такого понятия, как «загрузка страницы». Поэтому проверка наличия <body> в ответе может быть не всегда уместна для всех случаев использования. Обратитесь к часто задаваемым вопросам Selenium и документации Selenium для получения дополнительной информации.
Функции тестовых случаев
Клиент по умолчанию для тестирования
-
SimpleTestCase.client
Каждый тестовый случай в экземпляре django.test.*TestCase имеет доступ к экземпляру клиента тестирования Django. К этому клиенту можно обратиться как к self.client. Этот клиент пересоздаётся для каждого теста, поэтому вам не нужно беспокоиться о сохранении состояния (например, cookie) от одного теста к другому.
Это означает, что вместо создания экземпляра Client в каждом тесте:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
…вы можете обратиться к self.client, как показано ниже:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
Настройка клиента тестирования
-
SimpleTestCase.client_class
Если вы хотите использовать другой класс Client (например, подкласс с настраиваемым поведением), используйте атрибут класса client_class:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Загрузка фикстур
-
TransactionTestCase.fixtures
Класс тестового случая для веб-сайта с базой данных мало пригоден, если в базе данных нет данных. Тесты более читаемы и поддерживаются, если создавать объекты с помощью ORM, например, в TestCase.setUpTestData(), но вы также можете использовать фикстуры.
Фикстура — это набор данных, который Django знает как импортировать в базу данных. Например, если у вашего сайта есть учётные записи пользователей, вы можете создать фикстуру фальшивых учётных записей пользователей, чтобы заполнить базу данных во время тестов.
Самый простой способ создать фикстуру — использовать команду manage.py dumpdata. Это предполагает, что в вашей базе данных уже есть данные. Подробнее см. dumpdata
documentation.
После создания фикстуры и размещения её в каталоге fixtures в одном из ваших INSTALLED_APPS, вы можете использовать её в своих модульных тестах, указав атрибут класса fixtures в своём подклассе django.test.TestCase:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ["mammals.json", "birds"]
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Вот что конкретно произойдёт:
- Во время
setUpClass()все именованные фикстуры устанавливаются. В этом примере Django установит любые JSON-фикстуры, названныеmammals, а затем любые фикстуры, названныеbirds. Подробнее о определении и установке фикстур см. раздел Фикстуры.
Для большинства модульных тестов, использующих TestCase, Django ничего больше не нужно делать, потому что транзакции используются для очистки базы данных после каждого теста по соображениям производительности. Но для TransactionTestCase будут выполнены следующие действия:
- В конце каждого теста Django очищает базу данных, возвращая её в состояние, в котором она находилась непосредственно после вызова
migrate. - Для каждого последующего теста фикстуры будут загружаться заново перед выполнением
setUp().
В любом случае, вы можете быть уверены, что результат теста не повлияет на другой тест или порядок выполнения тестов.
По умолчанию фикстуры загружаются только в базу данных default. Если вы используете несколько баз данных и установили TransactionTestCase.databases, фикстуры будут загружены во все указанные базы данных.
Для TransactionTestCase фикстуры были доступны во время setUpClass().
Настройка URLconf
Если ваш модуль предоставляет представления, вы можете включить тесты, которые используют клиента тестирования для проверки этих представлений. Однако конечный пользователь свободен развертывать представления вашего модуля в любом URL по своему выбору. Это означает, что ваши тесты не могут полагаться на то, что ваши представления будут доступны по определённому URL. Для настройки URLconf используйте декоратор @override_settings(ROOT_URLCONF=...) для вашего класса или метода теста.
Поддержка нескольких баз данных
-
TransactionTestCase.databases
Django настраивает тестовую базу данных, соответствующую каждой базе данных, определённой в DATABASES определении в файле настроек и упомянутой хотя бы одним тестом через databases.
Однако большая часть времени, затрачиваемого на запуск Django-тестов, используется для вызова flush, который гарантирует, что база данных чистая в конце каждого теста. При наличии нескольких баз данных требуются несколько очисток (по одной для каждой базы данных), что может быть длительным процессом, особенно если ваши тесты не должны проверять работу с несколькими базами данных.
В целях оптимизации Django очищает только базу данных default в конце каждого теста. Если ваш проект содержит несколько баз данных и у вас есть тест, требующий очистки всех баз данных, вы можете использовать атрибут databases в наборе тестов для запроса очистки дополнительных баз данных.
Например:
class TestMyViews(TransactionTestCase):
databases = {"default", "other"}
def test_index_page_view(self):
call_some_test_code()
Этот класс тестового случая очистит тестовые базы данных default и other после запуска test_index_page_view. Вы также можете использовать '__all__' для указания необходимости очистки всех тестовых баз данных.
Флаг databases также управляет тем, в какие базы данных загружаются TransactionTestCase.fixtures. По умолчанию фикстуры загружаются только в базу данных default.
Запросы к базам данных, не входящим в databases, приведут к ошибкам утверждения, чтобы предотвратить утечку состояния между тестами.
-
TestCase.databases
По умолчанию только база данных default будет обернута в транзакцию во время выполнения TestCase, и попытки запроса к другим базам данных приведут к ошибкам утверждения, чтобы предотвратить утечку состояния между тестами.
Используйте атрибут класса databases в классе теста, чтобы запросить обёртку транзакции для баз данных, отличных от default.
Например:
class OtherDBTests(TestCase):
databases = {"other"}
def test_other_db_query(self): ...
Этот тест позволит выполнять запросы только к базе данных other. Как и для SimpleTestCase.databases и TransactionTestCase.databases, константа '__all__' может быть использована для указания того, что тест должен допускать запросы ко всем базам данных.
Переопределение настроек
Предупреждение
Используйте следующие функции для временного изменения значения настроек в тестах. Не изменяйте django.conf.settings напрямую, так как Django не восстановит исходные значения после таких манипуляций.
-
SimpleTestCase.settings()[source]
Для целей тестирования часто бывает полезно временно изменить настройку и вернуться к исходному значению после выполнения кода теста. Для этого случая Django предоставляет стандартный менеджер контекста Python (см. PEP 343), называемый settings(), который можно использовать так:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/accounts/login/?next=/sekrit/")
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL="/other/login/"):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
Этот пример переопределит настройку LOGIN_URL для кода в блоке with и сбросит её значение к предыдущему состоянию после этого.
-
SimpleTestCase.modify_settings()[source]
Может быть неудобно переопределять настройки, содержащие список значений. На практике часто достаточно добавить или удалить значения. Django предоставляет менеджер контекста modify_settings() для более удобных изменений настроек:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
"remove": [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
],
}
):
response = self.client.get("/")
# ...
Для каждого действия вы можете указать список значений или строку. Когда значение уже существует в списке, append и prepend не имеют эффекта; также не имеет эффекта remove, когда значение не существует.
-
override_settings(**kwargs)[source]
В случае, если вы хотите переопределить настройку для метода теста, Django предоставляет декоратор override_settings() (см. PEP 318). Он используется так:
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL="/other/login/")
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
Декоратор также может применяться к классам TestCase:
from django.test import TestCase, override_settings
@override_settings(LOGIN_URL="/other/login/")
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
-
modify_settings(*args, **kwargs)[source]
Аналогично, Django предоставляет декоратор modify_settings():
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
def test_cache_middleware(self):
response = self.client.get("/")
# ...
Декоратор также может применяться к классам тестовых случаев:
from django.test import TestCase, modify_settings
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get("/")
# ...
Примечание
При применении к классу эти декораторы изменяют класс напрямую и возвращают его; они не создают и не возвращают измененную копию. Поэтому, если вы попытаетесь изменить вышеприведённые примеры, чтобы присвоить возвращаемое значение другому имени, помимо LoginTestCase или MiddlewareTestCase, вас может удивить тот факт, что исходные классы тестовых случаев по-прежнему одинаково затронуты декоратором. Для данного класса modify_settings() всегда применяется после override_settings().
Предупреждение
Файл настроек содержит некоторые настройки, которые используются только во время инициализации внутренних компонентов Django. Если вы измените их с помощью override_settings, настройка меняется, если вы обращаетесь к ней через модуль django.conf.settings, однако внутренние компоненты Django обращаются к ней по-другому. По сути, использование override_settings() или modify_settings() с этими настройками, вероятно, не даст ожидаемого результата.
Мы не рекомендуем изменять настройку DATABASES. Изменение настройки CACHES возможно, но немного сложно, если вы используете внутренние компоненты, которые используют кеширование, например, django.contrib.sessions. Например, вам нужно будет повторно инициализировать бэкенд сессий в тесте, использующем кэшированные сессии и переопределяющем CACHES.
Наконец, избегайте алиасинга ваших настроек как констант на уровне модуля, так как override_settings() не будет работать с такими значениями, поскольку они оцениваются только в первый раз, когда модуль импортируется.
Вы также можете смоделировать отсутствие настройки, удалив её после переопределения настроек, как в этом примере:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
При переопределении настроек убедитесь, что обрабатываются случаи, когда код вашего приложения использует кэш или аналогичную функцию, которая сохраняет состояние, даже если настройка изменена. Django предоставляет сигнал django.test.signals.setting_changed, который позволяет регистрировать обратные вызовы для очистки и иного сброса состояния при изменении настроек.
Сам Django использует этот сигнал для сброса различных данных:
Переопределенные настройки | Сброс данных |
|---|---|
USE_TZ, TIME_ZONE | Часовой пояс баз данных |
TEMPLATES | Движки шаблонов |
FORM_RENDERER | По умолчанию рендерер |
SERIALIZATION_MODULES | Кэш сериализаторов |
LOCALE_PATHS, LANGUAGE_CODE | Перевод по умолчанию и загруженные переводы |
STATIC_ROOT, STATIC_URL, STORAGES | Настройки хранилищ |
Добавлен сброс рендерера по умолчанию при изменении настройки FORM_RENDERER.
Изоляция приложений
-
utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None) -
Регистрирует модели, определённые внутри обернутого контекста, в собственном изолированном реестре
apps. Эта функция полезна при создании моделей для тестов, так как классы будут чисто удалены после этого, и нет риска конфликтов имён.Метки приложений, которые должен содержать изолированный реестр, необходимо передавать в качестве отдельных аргументов. Вы можете использовать
isolate_apps()в качестве декоратора или менеджера контекста. Например:from django.db import models from django.test import SimpleTestCase from django.test.utils import isolate_apps class MyModelTests(SimpleTestCase): @isolate_apps("app_label") def test_model_definition(self): class TestModel(models.Model): pass ...… или:
with isolate_apps("app_label"): class TestModel(models.Model): pass ...Форма декоратора также может применяться к классам.
Можно указать два необязательных ключевых аргумента:
-
attr_name: атрибут, которому присваивается изолированный реестр, если используется в качестве декоратора класса. -
kwarg_name: ключевой аргумент, передающий изолированный реестр, если используется как декоратор функции.
Временный
Appsэкземпляр, используемый для изоляции регистрации моделей, можно получить как атрибут при использовании как декоратора класса, используя параметрattr_name:@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: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) -
Очистка тестового ящика вывода почты
Если вы используете любой из пользовательских классов Django TestCase, тестовый прогон очистит содержимое тестового ящика вывода электронной почты в начале каждого тестового случая.
Для получения более подробной информации об услугах электронной почты во время тестов см. Услуги электронной почты ниже.
Утверждения
Как обычный класс unittest.TestCase Python реализует методы утверждения, такие как assertTrue() и assertEqual(), пользовательский класс Django TestCase предоставляет ряд пользовательских методов утверждения, полезных для тестирования веб-приложений:
Сообщения об ошибках, генерируемые большинством этих методов утверждения, могут быть настраиваемыми с помощью аргумента msg_prefix. Эта строка будет добавляться в начало любого сообщения об ошибке, сгенерированного утверждением. Это позволяет предоставить дополнительные сведения, которые могут помочь определить местоположение и причину ошибки в наборе тестов.
-
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[source] - SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)
-
Утверждает, что выполнение
callableвызываетexpected_exceptionи чтоexpected_messageсодержится в сообщении об ошибке. Любой другой результат отображается как ошибка. Это упрощенная версияunittest.TestCase.assertRaisesRegex()с разницей в том, чтоexpected_messageне обрабатывается как регулярное выражение.Если указаны только параметры
expected_exceptionиexpected_message, возвращается менеджер контекста, чтобы код, который тестируется, мог быть написан в строке, а не как функция:with self.assertRaisesMessage(ValueError, "invalid literal for int()"): int("a")
-
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)[source] - SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
-
Аналогично
SimpleTestCase.assertRaisesMessage(), но дляassertWarnsRegex()вместоassertRaisesRegex().
-
SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[source] -
Утверждает, что поле формы ведет себя правильно с различными входными данными.
- Параметры:
-
- fieldclass – класс поля, подлежащего тестированию.
- valid – словарь, сопоставляющий допустимые входные данные с ожидаемыми очищенными значениями.
- invalid – словарь, сопоставляющий недопустимые входные данные с одним или несколькими сообщениями об ошибке.
- field_args – аргументы, переданные для создания поля.
- field_kwargs – ключевые аргументы, переданные для создания поля.
-
empty_value – ожидаемый очищенный вывод для входных данных в
empty_values.
Например, следующий код проверяет, что поле
EmailFieldпринимаетa@a.comкак допустимый адрес электронной почты, но отклоняетaaaс разумным сообщением об ошибке:self.assertFieldOutput( EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]} )
-
SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')[source] -
Утверждает, что поле на форме вызывает указанный список ошибок.
form— экземплярForm. Форма должна быть связанной, но не обязательно валидированной (assertFormError()автоматически вызоветfull_clean()для формы).field— имя поля на форме для проверки. Чтобы проверить ошибки формы без поля, используйтеfield=None.errors— список всех строк ошибок, которые, как ожидается, будут у поля. Вы также можете передать одну строку ошибки, если ожидается только одна ошибка, что означает, чтоerrors='error message'то же самое, что иerrors=['error message'].
-
SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='')[source] -
Утверждает, что
formsetвызывает указанный список ошибок при рендеринге.formset— экземплярFormSet. Набор форм должен быть связан, но не обязательно валидирован (assertFormSetError()автоматически вызоветfull_clean()для набора форм).form_index— номер формы вFormSet(начиная с 0). Используйтеform_index=Noneдля проверки ошибок набора форм без поля, т. е. ошибок, полученных при вызовеformset.non_form_errors(). В этом случае вы также должны использоватьfield=None.fieldиerrorsимеют то же значение, что и параметрыassertFormError().
-
SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[source] -
Утверждает, что полученный
responseимеет указанныйstatus_codeи чтоtextсодержится в егоcontent. Еслиcountпредоставлен,textдолжно встречаться ровноcountраз в ответе.Установите
htmlнаTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном совпадении символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. Подробнее см.assertHTMLEqual().Изменено в Django 5.1:В более старых версиях сообщения об ошибках не содержали содержимое ответа.
-
SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[source] -
Утверждает, что полученный
responseимеет указанныйstatus_codeи чтоtextне содержится в егоcontent.Установите
htmlнаTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном совпадении символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. Подробнее см.assertHTMLEqual().Изменено в Django 5.1:В более старых версиях сообщения об ошибках не содержали содержимое ответа.
-
SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[source] -
Утверждает, что шаблон с заданным именем был использован при рендеринге ответа.
responseдолжен быть экземпляром ответа, возвращенным методомtest client.template_nameдолжна быть строкой, такой как'admin/index.html'.Аргумент
count— целое число, указывающее количество раз, когда должен рендериться шаблон. Значение по умолчанию —None, что означает, что шаблон должен быть рендерен один или несколько раз.Вы можете использовать его как менеджер контекста, например так:
with self.assertTemplateUsed("index.html"): render_to_string("index.html") with self.assertTemplateUsed(template_name="index.html"): render_to_string("index.html")
-
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[source] -
Утверждает, что шаблон с заданным именем не использовался при отрисовке ответа.
Вы можете использовать его как менеджер контекста так же, как и
assertTemplateUsed().
-
SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')[source] -
Утверждает, что два URL-адреса одинаковы, игнорируя порядок параметров строки запроса, за исключением параметров с одинаковым именем. Например,
/path/?x=1&y=2равно/path/?y=2&x=1, но/path/?a=1&a=2не равно/path/?a=2&a=1.
-
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[source] -
Утверждает, что возвращённый
responseимел код статуса переадресацииstatus_code, перенаправил наexpected_url(включая любые данныеGET), и что конечная страница была получена сtarget_status_code.Если ваш запрос использовал аргумент
follow, тоexpected_urlиtarget_status_codeбудут URL и кодом статуса для конечной точки цепочки переадресации.Если
fetch_redirect_responseравноFalse, то конечная страница не будет загружена. Поскольку тестовый клиент не может получать внешние URL-адреса, это особенно полезно, еслиexpected_urlне является частью вашего приложения Django.Схема обрабатывается корректно при сравнении двух URL-адресов. Если в местоположение, куда мы перенаправлены, не указана схема, используется схема исходного запроса. Если присутствует, схема в
expected_urlиспользуется для сравнения.
-
SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[source] -
Утверждает, что строки
html1иhtml2равны. Сравнение основано на семантике HTML. Сравнение учитывает следующие моменты:- Пробелы до и после тегов HTML игнорируются.
- Все типы пробелов считаются эквивалентными.
- Все открытые теги неявно закрываются, например, при закрытии окружающего тега или при окончании HTML-документа.
- Пустые теги эквивалентны их самозакрывающимся версиям.
- Порядок атрибутов HTML-элемента не имеет значения.
- Булевы атрибуты (например,
checked) без аргумента равны атрибутам, которые равны по имени и значению (см. примеры). - Текст, ссылки на символы и ссылки на сущности, которые ссылаются на один и тот же символ, эквивалентны.
Следующие примеры являются допустимыми тестами и не вызывают никаких
AssertionError:self.assertHTMLEqual( "<p>Hello <b>'world'!</p>", """<p> Hello <b>'world'! </b> </p>""", ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>', )html1иhtml2должны содержать HTML. Будет выброшено исключениеAssertionError, если один из них не может быть распарсен.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[source] -
Утверждает, что строки
html1иhtml2не равны. Сравнение основано на семантике HTML. См.assertHTMLEqual()для получения дополнительных сведений.html1иhtml2должны содержать HTML. Будет выброшено исключениеAssertionError, если один из них не может быть распарсен.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[source] -
Утверждает, что строки
xml1иxml2равны. Сравнение основано на семантике XML. АналогичноassertHTMLEqual(), сравнение выполняется на основе распарсенного содержимого, поэтому учитываются только семантические различия, а не синтаксические. При передаче некорректного XML в любом параметре всегда будет выбрасываться исключениеAssertionError, даже если обе строки идентичны.Декларация XML, тип документа, инструкции обработки и комментарии игнорируются. Сравниваются только корневой элемент и его потомки.
Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[source] -
Утверждает, что строки
xml1иxml2не равны. Сравнение основано на семантике XML. См.assertXMLEqual()для получения дополнительных сведений.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[source] -
Утверждает, что HTML-фрагмент
needleсодержится вhaystackодин раз.Если указан целочисленный аргумент
count, то дополнительно будет строго проверяться количествоneedleвхождений.Пробелы в большинстве случаев игнорируются, и порядок атрибутов не имеет значения. См.
assertHTMLEqual()для получения более подробной информации.Изменено в Django 5.1:В более старых версиях сообщения об ошибках не содержали
haystack.
-
SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='')[source] -
Новое в Django 5.1.
Утверждает, что HTML-фрагмент
needleне содержится вhaystack.Пробелы в большинстве случаев игнорируются, и порядок атрибутов не имеет значения. См.
assertHTMLEqual()для получения более подробной информации.
-
SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[source] -
Утверждает, что JSON-фрагменты
rawиexpected_dataравны. Обычные правила JSON относительно несущественных пробелов применяются, так как тяжёлая работа делегирована библиотекеjson.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[source] -
Утверждает, что JSON-фрагменты
rawиexpected_dataне равны. См.assertJSONEqual()для получения более подробной информации.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[source] -
Утверждает, что набор запросов
qsсоответствует определённому итерируемому набору значенийvalues.Если
transformпредоставлен,valuesсравнивается со списком, полученным путём примененияtransformк каждому членуqs.По умолчанию сравнение также зависит от порядка. Если
qsне предоставляет явного порядка, вы можете установить параметрorderedв значениеFalse, что превратит сравнение вcollections.Counterсравнение. Если порядок неопределён (если предоставленныйqsне упорядочен и сравнение выполняется с более чем одним упорядоченным значением), вызывается исключениеValueError.Выходные данные в случае ошибки могут быть настраиваемы с помощью аргумента
msg.
-
TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[source] -
Утверждает, что при вызове
funcс*argsи**kwargsвыполняетсяnumзапросов к базе данных.Если ключ
"using"присутствует вkwargs, он используется в качестве алиаса базы данных, для которого нужно проверить количество запросов:self.assertNumQueries(7, using="non_default_db")
Если вы хотите вызвать функцию с параметром
using, вы можете сделать это, обернув вызов вlambda, чтобы добавить дополнительный параметр:self.assertNumQueries(7, lambda: my_function(using=7))
Вы также можете использовать его как контекстный менеджер:
with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
Тегирование тестов
Вы можете добавить теги к своим тестам, чтобы легко запускать определённый подмножество. Например, вы можете маркировать тесты как быстрые или медленные:
from django.test import tag
class SampleTestCase(TestCase):
@tag("fast")
def test_fast(self): ...
@tag("slow")
def test_slow(self): ...
@tag("slow", "core")
def test_slow_but_core(self): ...
Вы также можете добавить теги к классу тестовых случаев:
@tag("slow", "core")
class SampleTestCase(TestCase): ...
Подклассы наследуют теги от суперклассов, а методы наследуют теги от своего класса. Учитывая:
@tag("foo")
class SampleTestCaseChild(SampleTestCase):
@tag("bar")
def test(self): ...
SampleTestCaseChild.test будет помечен тегами 'slow', 'core', 'bar' и 'foo'.
Затем вы можете выбрать, какие тесты запустить. Например, чтобы запустить только быстрые тесты:
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
Или чтобы запустить быстрые тесты и основные (даже если они медленные):
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
Вы также можете исключать тесты по тегу. Чтобы запустить тесты ядра, если они не медленные:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag имеет приоритет над test --tag, поэтому если у теста есть два тега, и вы выбираете один из них, а исключаете другой, тест не будет запущен.
Тестирование асинхронного кода
Если вам просто нужно протестировать вывод ваших асинхронных представлений, стандартный тестовый клиент запустит их внутри собственного асинхронного цикла без дополнительных действий с вашей стороны.
Однако, если вы хотите написать полностью асинхронные тесты для проекта Django, вам нужно учитывать несколько моментов.
Во-первых, ваши тесты должны быть async def методами в тестовом классе (чтобы предоставить им асинхронный контекст). Django автоматически обнаружит любые async def тесты и обернёт их так, чтобы они выполнялись в собственном цикле событий.
Если вы тестируете из асинхронной функции, вы также должны использовать асинхронный тестовый клиент. Он доступен как django.test.AsyncClient или как self.async_client в любом тесте.
-
class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[source]
AsyncClient имеет те же методы и сигнатуры, что и синхронный (обычный) тестовый клиент, с такими исключениями:
- При инициализации произвольные ключевые аргументы в
defaultsдобавляются непосредственно в ASGI-scope. -
Заголовки, переданные в виде ключевых аргументов
extra, не должны иметь префиксHTTP_, необходимый для синхронного клиента (см.Client.get()). Например, вот как установить заголовок HTTPAccept:>>> c = AsyncClient() >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
Был добавлен аргумент query_params.
Используя AsyncClient, любой метод, выполняющий запрос, должен быть ожидаемым:
async def test_my_thing(self):
response = await self.async_client.get("/some-url/")
self.assertEqual(response.status_code, 200)
Асинхронный клиент также может вызывать синхронные представления; он проходит через асинхронный путь запроса Django, который поддерживает оба. Любое представление, вызванное через AsyncClient, получит объект ASGIRequest для своего request, а не объект WSGIRequest, созданный обычным клиентом.
Предупреждение
Если вы используете декораторы тестов, они должны быть совместимы с асинхронностью, чтобы обеспечить их правильную работу. Встроенные декораторы Django будут работать правильно, но сторонние могут, кажется, не выполняться (они «обернут» неправильную часть потока выполнения, а не ваш тест).
Если вам нужно использовать эти декораторы, то вы должны декорировать ваши тестовые методы с помощью async_to_sync() внутри них:
from asgiref.sync import async_to_sync
from django.test import TestCase
class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self): ...
Тестирование служб электронной почты
Если какие-либо ваши представления Django отправляют электронную почту с помощью функциональности отправки почты Django, вам, вероятно, не нужно отправлять электронную почту каждый раз, когда вы запускаете тест, использующий это представление. По этой причине тестовый запуск Django автоматически перенаправляет всю отправленную Django почту в тестовый ящик. Это позволяет проверить все аспекты отправки почты — от количества отправленных сообщений до содержимого каждого сообщения — без фактической отправки сообщений.
Тестовый запуск добивается этого, прозрачно заменяя стандартный почтовый бэкенд тестовым бэкендом. (Не беспокойтесь — это никак не повлияет на любые другие почтовые отправители за пределами Django, такие как почтовый сервер вашей машины, если вы его используете.)
-
django.core.mail.outbox
Во время выполнения тестов каждое отправленное сообщение сохраняется в django.core.mail.outbox. Это список всех экземпляров EmailMessage, которые были отправлены. Атрибут outbox — это специальный атрибут, создаваемый только при использовании тестового почтового бэкенда. Он обычно не существует как часть модуля django.core.mail, и вы не можете импортировать его напрямую. Приведённый ниже код показывает, как правильно получить доступ к этому атрибуту.
Вот пример теста, который проверяет django.core.mail.outbox на длину и содержимое:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
"Subject here",
"Here is the message.",
"from@example.com",
["to@example.com"],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, "Subject here")
Как отмечалось ранее, тестовый ящик очищается в начале каждого теста в Django *TestCase. Чтобы очистить ящик вручную, присвойте пустой список mail.outbox:
from django.core import mail # Empty the test outbox mail.outbox = []
Команды управления
Команды управления можно тестировать с помощью функции call_command(). Выходные данные можно перенаправить в объект StringIO:
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command("closepoll", poll_ids=[1], stdout=out)
self.assertIn('Successfully closed poll "1"', out.getvalue())
Пропуск тестов
Библиотека unittest предоставляет декораторы @skipIf и @skipUnless, которые позволяют пропускать тесты, если заранее известно, что эти тесты не пройдут в определённых условиях.
Например, если ваш тест требует определённой дополнительной библиотеки для успешного выполнения, вы можете украсить тестовый случай с помощью @skipIf. Затем система выполнения тестов сообщит, что тест не был выполнен и почему, вместо того чтобы провалить тест или вообще его пропустить.
В дополнение к этим возможностям пропуска тестов Django предоставляет два дополнительных декоратора пропуска. Вместо проверки общего булева значения, эти декораторы проверяют возможности базы данных и пропускают тест, если база данных не поддерживает определённую именованную функцию.
Декораторы используют строковый идентификатор для описания функций базы данных. Эта строка соответствует атрибутам класса функций подключения к базе данных. Смотрите класс django.db.backends.base.features.BaseDatabaseFeatures для получения полного списка функций базы данных, которые могут быть использованы для пропуска тестов.
-
skipIfDBFeature(*feature_name_strings)[source]
Пропустить декорированный тест или TestCase, если все указанные функции базы данных поддерживаются.
Например, следующий тест не будет выполнен, если база данных поддерживает транзакции (например, он не будет выполняться в PostgreSQL, но будет в MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
-
skipUnlessDBFeature(*feature_name_strings)[source]
Пропустить декорированный тест или TestCase, если какая-либо из указанных функций базы данных не поддерживается.
Например, следующий тест будет выполняться только в том случае, если база данных поддерживает транзакции (например, он будет выполняться в PostgreSQL, но не в MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipUnlessDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/testing/tools/