Инструменты тестирования
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 с помощью клиента тестирования, можно создать экземпляр клиента тестирования, который их выполняет. Для этого передайте аргумент
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, **defaults) -
Тестовый HTTP-клиент. Принимает несколько аргументов, которые могут настроить поведение.
headersпозволяет указать значения по умолчанию для заголовков, которые будут отправляться с каждым запросом. Например, чтобы задать заголовокUser-Agent:client = Client(headers={"user-agent": "curl/7.79.1"})Произвольные ключевые аргументы в
**defaultsустанавливают переменные окружения WSGI переменные окружения. Например, чтобы установить имя скрипта:client = Client(SCRIPT_NAME="/app/")
Примечание
Ключевые аргументы, начинающиеся с префикса
HTTP_, устанавливаются как заголовки, но для удобочитаемости предпочтительнее использовать параметрheaders.Значения из ключевых аргументов
headersиextra, переданных вget(),post()и т. д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.Аргумент
enforce_csrf_checksможно использовать для тестирования защиты от CSRF (см. выше).Аргумент
raise_request_exceptionпозволяет контролировать, будут ли исключения, возникающие во время запроса, также генерироваться в тесте. По умолчаниюTrue.Аргумент
json_encoderпозволяет задать пользовательский кодировщик JSON для сериализации JSON, о которой рассказано вpost().После получения экземпляра
Client, можно вызвать любой из следующих методов:Изменено в Django 4.2:Добавлен параметр
headers.-
get(path, data=None, follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос GET по указанному
pathи возвращает объектResponse, описание которого приведено ниже.Пары ключ-значение в словаре
dataиспользуются для создания полезной нагрузки данных GET. Например:>>> c = Client() >>> c.get("/customers/details/", {"name": "fred", "age": 7})…приведет к выполнению запроса GET, эквивалентного:
/customers/details/?name=fred&age=7
Параметр
headersможет быть использован для указания заголовков, которые нужно отправить в запросе. Например:>>> c = Client() >>> c.get( ... "/customers/details/", ... {"name": "fred", "age": 7}, ... headers={"accept": "application/json"}, ... )…отправит HTTP-заголовок
HTTP_ACCEPTв представление деталей, что является хорошим способом тестирования путей кода, использующих методdjango.http.HttpRequest.accepts().Произвольные ключевые аргументы устанавливают WSGI переменные окружения. Например, для установки имени скрипта:
>>> c = Client() >>> c.get("/", SCRIPT_NAME="/app/")Если у вас уже есть аргументы GET в кодировке URL, вы можете использовать эту кодировку вместо аргумента data. Например, предыдущий запрос GET также можно отправить как:
>>> c = Client() >>> c.get("/customers/details/?name=fred&age=7")Если вы предоставите URL с закодированными данными GET и аргументом data, аргумент 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 4.2:Добавлен параметр
headers.
-
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос 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иextraработают так же, как и дляClient.get().Если в URL, который вы запрашиваете с помощью POST, содержатся закодированные параметры, эти параметры будут доступны в данных request.GET. Например, если вы выполните запрос:
>>> c.post("/login/?visitor=true", {"name": "fred", "passwd": "secret"})… обработчик представления этого запроса может получить имя пользователя и пароль из request.POST и определить, был ли пользователь гостем, используя request.GET.
Если вы установите
followвTrue, клиент будет следовать всем перенаправлениям, и в объекте ответа будет установлен атрибутredirect_chain, содержащий кортежи промежуточных URL и кодов состояния.Если вы установите
secureвTrue, клиент будет эмулировать запрос HTTPS.Изменено в Django 4.2:Добавлен параметр
headers.
-
head(path, data=None, follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос HEAD по указанному
pathи возвращает объектResponse. Этот метод работает так же, какClient.get(), включая параметрыfollow,secure,headersиextra, за исключением того, что он не возвращает тело сообщения.Изменено в Django 4.2:Добавлен параметр
headers.
-
options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос OPTIONS по указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Если предоставлен параметр
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается вcontent_type.Параметры
follow,secure,headersиextraдействуют так же, как дляClient.get().Изменено в Django 4.2:Добавлен параметр
headers.
-
put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос PUT по указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Если предоставлен параметр
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается вcontent_type.Параметры
follow,secure,headersиextraдействуют так же, как и дляClient.get().Изменено в Django 4.2:Добавлен параметр
headers.
-
-
patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос PATCH по указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Параметры
follow,secure,headers, иextraработают так же, как и дляClient.get().Изменено в Django 4.2:Добавлен параметр
headers.
-
delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос DELETE по указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Если предоставлен параметр
data, он используется в качестве тела запроса, и устанавливается заголовокContent-Typeсо значениемcontent_type.Параметры
follow,secure,headers, иextraработают так же, как и дляClient.get().Изменено в Django 4.2:Добавлен параметр
headers.
-
trace(path, follow=False, secure=False, *, headers=None, **extra) -
Выполняет запрос TRACE по указанному
pathи возвращает объектResponse. Полезно для моделирования диагностических зондирований.В отличие от других методов запросов,
dataне предоставляется как ключевой параметр, чтобы соответствовать RFC 9110#section-9.3.8, который предписывает, что запросы TRACE не должны иметь тело.Параметры
follow,secure,headers, иextraработают так же, как и дляClient.get().Изменено в Django 4.2:Добавлен параметр
headers.
-
login(**credentials) -
Если ваш сайт использует систему аутентификации 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) -
Если ваш сайт использует систему аутентификации Django, вы можете использовать метод
force_login()для моделирования входа пользователя на сайт. Используйте этот метод вместоlogin(), когда тест требует входа пользователя, и подробности о том, как пользователь вошел, не важны.В отличие от
login(), этот метод пропускает шаги аутентификации и проверки: неактивные пользователи (is_active=False) могут войти, и учетные данные пользователя не требуются.Атрибут пользователя
backendбудет установлен в значение аргументаbackend(который должен быть строкой с путём к Python-классу) или в значениеsettings.AUTHENTICATION_BACKENDS[0], если значение не предоставлено. Функцияauthenticate(), вызываемаяlogin(), обычно аннотирует пользователя таким образом.Этот метод быстрее, чем
login(), поскольку обходятся дорогостоящие алгоритмы хэширования паролей. Кроме того, вы можете ускоритьlogin(), используя более слабый хэшер во время тестирования.
-
logout() -
Если ваш сайт использует систему аутентификации 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() запросами.
Политики истечения срока действия для этих cookie не соблюдаются. Если вы хотите, чтобы cookie истекла, удалите её вручную или создайте новый экземпляр Client (что фактически удалит все cookie).
Тестовый клиент имеет атрибуты, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Client.cookies -
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. См. документацию модуляhttp.cookiesдля получения дополнительной информации.
-
Client.session -
Объект, похожий на словарь, содержащий информацию о сессии. Подробности см. в документации по сессиям.
Для изменения сессии и сохранения её, нужно сохранить её в переменную (потому что каждый раз при обращении к этому свойству создаётся новый
SessionStore):def test_something(self): session = self.client.session session["somekey"] = "test" session.save()
Установка языка
При тестировании приложений, поддерживающих международные настройки и локализации, вы можете захотеть установить язык для запроса тестового клиента. Метод зависит от того, включён ли LocaleMiddleware.
Если middleware включён, язык можно установить, создав 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 определяет предпочтение языка.
Если middleware не включён, активный язык можно установить с помощью 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.")
Дополнительные сведения см. в Явное установление активного языка.
Пример
Следующий пример демонстрирует unit-тест с использованием тестового клиента:
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
Подкласс 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
TransactionTestCase наследуется от SimpleTestCase, чтобы добавить некоторые функции, специфичные для базы данных:
- Сброс базы данных до известного состояния в начале каждого теста для облегчения тестирования и использования ORM.
- Фикстуры базы данных
fixtures. - Пропуск тестов в зависимости от возможностей СУБД пропуск тестов.
- Дополнительные специализированные методы
assert*.
Класс TestCase Django — это более распространённый подкласс TransactionTestCase, который использует возможности транзакций базы данных, чтобы ускорить процесс сброса базы данных до известного состояния в начале каждого теста. Однако следствием этого является то, что некоторые поведения базы данных не могут быть протестированы внутри класса Django TestCase. Например, нельзя проверить, что блок кода выполняется в транзакции, как это требуется при использовании select_for_update(). В таких случаях следует использовать TransactionTestCase.
TransactionTestCase и TestCase идентичны, за исключением способа сброса базы данных до известного состояния и возможности проверки эффектов коммита и отката:
TransactionTestCaseсбрасывает базу данных после выполнения теста, обнуляя все таблицы.TransactionTestCaseможет вызывать коммит и откат и наблюдать за эффектами этих вызовов на базе данных.TestCase, с другой стороны, не обнуляет таблицы после теста. Вместо этого он помещает код теста в транзакцию базы данных, которая отменяется в конце теста. Это гарантирует, что откат в конце теста восстановит базу данных в исходное состояние.
Предупреждение
TestCase, работающий с базой данных, не поддерживающей откат (например, MySQL с хранилищем MyISAM), и все экземпляры TransactionTestCase, будут отменены в конце теста удалением всех данных из тестовой базы данных.
Приложения не увидят перезагрузку своих данных; если вам нужна эта функциональность (например, сторонним приложениям следует её включить), вы можете установить serialized_rollback = True внутри блока TestCase.
TestCase
-
class TestCase
Этот класс чаще всего используется для написания тестов в Django. Он наследуется от TransactionTestCase (и, следовательно, от SimpleTestCase). Если ваше приложение Django не использует базу данных, используйте SimpleTestCase.
Класс:
- Оборачивает тесты в два вложенных блока
atomic(): один для всего класса и один для каждого теста. Следовательно, если вы хотите протестировать определённое поведение транзакции базы данных, используйтеTransactionTestCase. - Проверяет отложенные ограничения базы данных в конце каждого теста.
Он также предоставляет дополнительный метод:
-
classmethod TestCase.setUpTestData() -
Указанный выше блок уровня класса
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) -
Возвращает менеджер контекста, который захватывает коллбеки
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
LiveServerTestCase в основном делает то же самое, что и TransactionTestCase, но с одной дополнительной функцией: он запускает живой сервер Django на заднем плане при настройке и закрывает его при завершении. Это позволяет использовать автоматические тестовые клиенты, отличные от Django dummy client, такие как, например, клиент Selenium, для выполнения серии функциональных тестов в браузере и имитации действий реального пользователя.
Живой сервер слушает на localhost и привязывается к порту 0, который использует свободный порт, назначенный операционной системой. К URL-адресу сервера можно получить доступ с помощью self.live_server_url во время тестов.
Чтобы продемонстрировать, как использовать LiveServerTestCase, давайте напишем Selenium-тест. Прежде всего, вам необходимо установить пакет selenium в ваш путь Python:
$ python -m pip install selenium
...\> py -m pip install selenium
Затем добавьте тест, основанный на 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 дождаться, пока тег <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. Этот клиент пересоздается для каждого теста, поэтому вам не нужно беспокоиться о том, что состояние (например, куки) будет передаваться из одного теста в другой.
Это означает, что вместо создания экземпляра 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()
Вот что именно произойдёт:
- В начале каждого теста, перед запуском
setUp(), Django очистит базу данных, вернув базу данных в состояние, в котором она находилась непосредственно после вызоваmigrate. - Затем все указанные фикстуры будут установлены. В данном примере Django установит все JSON-фикстуры с именем
mammals, а затем — все фикстуры с именемbirds. Обратитесь к разделу Фикстуры для получения более подробной информации о определении и установке фикстур.
Из соображений производительности, TestCase загружает фикстуры один раз для всего тестового класса, перед setUpTestData(), вместо того, чтобы делать это перед каждым тестом, и использует транзакции для очистки базы данных перед каждым тестом. В любом случае, вы можете быть уверены, что результат теста не будет зависеть от другого теста или порядка выполнения тестов.
По умолчанию фикстуры загружаются только в базу данных default. Если вы используете несколько баз данных и установили TransactionTestCase.databases, фикстуры будут загружены во все указанные базы данных.
Настройка URLconf
Если ваше приложение предоставляет представления, вам может потребоваться включить тесты, которые используют тестовый клиент для проверки этих представлений. Однако конечный пользователь может развернуть представления в вашем приложении на любом URL-адресе по своему выбору. Это означает, что ваши тесты не могут полагаться на то, что ваши представления будут доступны по определенному URL-адресу. Используйте декоратор @override_settings(ROOT_URLCONF=...) для настройки URLconf.
Поддержка нескольких баз данных
-
TransactionTestCase.databases
Django настраивает тестовую базу данных, соответствующую каждой базе данных, определённой в определении DATABASES в ваших настройках и упоминаемой хотя бы одним тестом через databases.
Однако большая часть времени, затрачиваемого на выполнение Django TestCase, тратится на вызов 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()
Для целей тестирования часто бывает полезно временно изменить настройку и вернуть исходное значение после выполнения кода тестирования. Для этого 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()
Переопределение настроек, содержащих список значений, может оказаться неудобным. На практике часто достаточно добавления или удаления значений. 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)
В случае необходимости переопределения настройки для метода теста 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)
Аналогично, 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 | Движки шаблонов |
| SERIALIZATION_MODULES | Кэш сериализаторов |
| LOCALE_PATHS, LANGUAGE_CODE | Стандартный перевод и загруженные переводы |
| DEFAULT_FILE_STORAGE, STATICFILES_STORAGE, STATIC_ROOT, STATIC_URL, STORAGES | Настройка хранилищ |
Изоляция приложений
-
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) -
Очистка тестового ящика вывода
Если вы используете любые из пользовательских классов TestCase Django, тестовый запуск очистит содержимое тестового ящика вывода электронной почты в начале каждого тестового случая.
Для получения дополнительной информации об услугах электронной почты во время тестов см. Услуги электронной почты ниже.
Утверждения
Так как стандартный класс Python unittest.TestCase реализует методы утверждения, такие как assertTrue() и assertEqual(), пользовательский класс Django TestCase предоставляет ряд пользовательских методов утверждений, которые полезны для тестирования веб-приложений:
Сообщения об ошибках, предоставляемые большинством этих методов утверждений, можно настроить с помощью аргумента msg_prefix. Эта строка будет добавляться в начало любого сообщения об ошибке, сгенерированного утверждением. Это позволяет вам предоставлять дополнительные сведения, которые могут помочь определить местоположение и причину сбоя в вашем наборе тестов.
-
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs) -
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) -
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message) -
Аналогично
SimpleTestCase.assertRaisesMessage(), но дляassertWarnsRegex()вместоassertRaisesRegex().
-
SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='') -
Утверждает, что поле формы ведет себя корректно при различных входных данных.
Параметры: - 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='') -
Утверждает, что поле на форме вызывает предоставленный список ошибок.
form— это экземплярForm. Форма должна быть связанной, но не обязательно проверенной (assertFormError()автоматически вызоветfull_clean()для формы).field— это имя поля на форме, которое нужно проверить. Чтобы проверить не связанные с полями ошибки формы, используйтеfield=None.errors— это список всех ожидаемых сообщений об ошибках поля. Вы также можете передать одну строку ошибки, если ожидается только одна ошибка, что означает, чтоerrors='error message'эквивалентноerrors=['error message'].Изменено в Django 4.1:В более ранних версиях использование пустого списка ошибок с
assertFormError()всегда считалось успешным, независимо от того, есть ли у поля ошибки или нет. Начиная с Django 4.1, использованиеerrors=[]считается успешным только в том случае, если у поля фактически нет ошибок.Django 4.1 также изменил поведение
assertFormError(), когда у поля несколько ошибок. В более ранних версиях, если у поля было несколько ошибок, и вы проверяли только некоторые из них, тест проходил. Начиная с Django 4.1, список ошибок должен точно совпадать с фактическими ошибками поля.Устарело начиная с версии 4.1: Поддержка передачи объекта ответа и имени формы в
assertFormError()устарела и будет удалена в Django 5.0. Используйте экземпляр формы напрямую вместо этого.
-
SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='') -
Утверждает, что
formsetвызывает предоставленный список ошибок при рендеринге.formset— это экземплярFormSet. Formset должен быть связан, но не обязательно валидирован (assertFormSetError()автоматически вызоветfull_clean()для formset).form_index— это номер формы вFormSet(начиная с 0). Используйтеform_index=Noneдля проверки ошибок formset, не связанных с формами, т.е. ошибок, которые вы получаете при вызовеformset.non_form_errors(). В этом случае вы также должны использоватьfield=None.fieldиerrorsимеют то же значение, что и параметры вassertFormError().Устарело начиная с версии 4.1: Поддержка передачи объекта ответа и имени formset в
assertFormSetError()устарела и будет удалена в Django 5.0. Используйте экземпляр formset напрямую вместо этого.Устарело начиная с версии 4.2: Метод утверждения
assertFormsetError()устарел. ИспользуйтеassertFormSetError()вместо него.
-
SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False) -
Утверждает, что
responseпроизвел указанныйstatus_codeи чтоtextпоявляется в егоcontent. Еслиcountпредоставлено,textдолжно появляться ровноcountраз в ответе.Установите
htmlнаTrue, чтобы обработатьtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. Подробнее см.assertHTMLEqual().
-
SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False) -
Утверждает, что
responseпроизвел указанныйstatus_codeи чтоtextне появляется в егоcontent.Установите
htmlнаTrue, чтобы обработатьtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. Подробнее см.assertHTMLEqual().
-
SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None) -
Утверждает, что шаблон с указанным именем использовался при рендеринге ответа.
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='') -
Утверждает, что шаблон с указанным именем не использовался при рендеринге ответа.
Вы можете использовать это как менеджер контекста так же, как и
assertTemplateUsed().
-
SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='') -
Утверждает, что два 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) -
Утверждает, что
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) -
Утверждает, что строки
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) -
Утверждает, что строки
html1иhtml2не равны. Сравнение основано на семантике HTML. См.assertHTMLEqual()для получения подробной информации.html1иhtml2должны содержать HTML. Будет возбуждено исключениеAssertionError, если один из них не может быть проанализирован.Вывод при ошибке можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) -
Утверждает, что строки
xml1иxml2равны. Сравнение основано на семантике XML. АналогичноassertHTMLEqual(), сравнение выполняется на проанализированном содержимом, поэтому учитываются только семантические различия, а не синтаксические. Если в любой параметр передается некорректный XML, всегда возбуждается исключениеAssertionError, даже если обе строки идентичны.Декларация XML, тип документа, инструкции обработки и комментарии игнорируются. Сравниваются только корневой элемент и его потомки.
Вывод при ошибке можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) -
Утверждает, что строки
xml1иxml2не равны. Сравнение основано на семантике XML. См.assertXMLEqual()для получения подробной информации.Вывод при ошибке можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='') -
Утверждает, что HTML-фрагмент
needleсодержится вhaystackодин раз.Если указан целочисленный аргумент
count, то дополнительно будет строго проверяться количествоneedleвхождений.Пробелы в большинстве случаев игнорируются, а порядок атрибутов не имеет значения. См.
assertHTMLEqual()для получения более подробной информации.
-
SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) -
Утверждает, что JSON-фрагменты
rawиexpected_dataравны. Стандартные правила JSON для игнорирования пробелов применяются, так как тяжелая обработка делегирована библиотекеjson.Вывод при ошибке можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None) -
Утверждает, что JSON-фрагменты
rawиexpected_dataне равны. См.assertJSONEqual()для получения дополнительной информации.Вывод при ошибке можно настроить с помощью аргумента
msg.
-
TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None) -
Утверждает, что набор запросов
qsсоответствует определённому итерируемому набору значенийvalues.Если
transformуказано,valuesсравнивается со списком, полученным путём примененияtransformк каждому элементуqs.По умолчанию сравнение также зависит от порядка. Если
qsне предоставляет явного порядка, можно установить параметрorderedвFalse, что превращает сравнение в сравнение по содержанию. Если порядок неопределён (если предоставленныйqsне упорядочен и сравнение проводится с более чем одним упорядоченным значением), возбуждается исключениеValueError.Вывод при ошибке можно настроить с помощью аргумента
msg.Устарело начиная с версии 4.2: Метод утверждения
assertQuerysetEqual()устарел. ИспользуйтеassertQuerySetEqual()вместо него.
-
TransactionTestCase.assertNumQueries(num, func, *args, **kwargs) -
Утверждает, что при вызове
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
Также можно исключить тесты по тегу. Чтобы запустить тесты 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, **defaults)
AsyncClient имеет те же методы и сигнатуры, что и синхронный (обычный) тестовый клиент, с двумя исключениями:
- При инициализации произвольные ключевые аргументы в
defaultsдобавляются непосредственно в область ASGI. - Параметр
followне поддерживается. -
Заголовки, переданные как ключевые аргументы
extra, не должны иметь префиксHTTP_, необходимый для синхронного клиента (см.Client.get()). Например, вот как установить HTTP-заголовокAccept:>>> c = AsyncClient() >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
Добавлен параметр headers.
Используя 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", stdout=out)
self.assertIn("Expected output", out.getvalue())
Пропуск тестов
Библиотека unittest предоставляет декораторы @skipIf и @skipUnless, которые позволяют пропустить тесты, если заранее известно, что эти тесты могут завершиться неудачей в определенных условиях.
Например, если ваш тест требует определенной дополнительной библиотеки для успеха, вы можете украсить тестовый случай с помощью @skipIf. Затем тестовый запуск сообщит, что тест не был выполнен и почему, вместо того, чтобы завершить тест неудачей или опустить его вообще.
Для дополнения этих поведений пропуска тестов Django предоставляет два дополнительных декоратора пропуска. Вместо проверки универсального булева значения, эти декораторы проверяют возможности базы данных и пропускают тест, если база данных не поддерживает определенное именованное свойство.
Декораторы используют строковый идентификатор для описания свойств базы данных. Эта строка соответствует атрибутам класса функций свойств подключения к базе данных. Полный список свойств базы данных, которые могут быть использованы в качестве основы для пропуска тестов, см. в django.db.backends.base.features.BaseDatabaseFeatures class.
-
skipIfDBFeature(*feature_name_strings)
Пропустить украшенный тест или TestCase если все именованные свойства базы данных поддерживаются.
Например, следующий тест не будет выполнен, если база данных поддерживает транзакции (например, он не будет выполнен под PostgreSQL, но будет под MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
-
skipUnlessDBFeature(*feature_name_strings)
Пропустить украшенный тест или 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/4.2/topics/testing/tools/