Инструменты тестирования
Django предоставляет небольшой набор инструментов, которые полезны при написании тестов.
Клиент для тестирования
Клиент для тестирования — это класс Python, который имитирует веб-браузер, позволяя программно тестировать представления и взаимодействовать с приложением Django.
С помощью клиента для тестирования можно:
- Имитировать запросы GET и POST на URL и наблюдать за ответом — от низкоуровневого HTTP (заголовки и коды состояния результата) до содержимого страницы.
- Просматривать цепочку перенаправлений (если они есть) и проверять URL и код состояния на каждом шаге.
- Проверить, что данный запрос отображается заданным шаблоном Django с контекстом шаблона, содержащим определенные значения.
Обратите внимание, что клиент для тестирования не предназначен для замены Selenium или других фреймворков «в браузере». У клиента Django для тестирования другой фокус. Короче говоря:
- Используйте клиент для тестирования Django, чтобы убедиться, что отображается правильный шаблон и что шаблон получает правильные данные контекста.
- Используйте фреймворки в браузере, такие как 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-адресов клиент для тестирования использует конфигурацию URL, указанную в настройке
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, json_encoder=DjangoJSONEncoder, **defaults) -
Он не требует аргументов при создании. Однако вы можете использовать именованные аргументы для задания некоторых стандартных заголовков. Например, это отправит заголовок
User-AgentHTTP в каждом запросе:>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
Значения из именованных аргументов
extra, переданных вget(),post()и т.д., имеют приоритет над значениями по умолчанию, переданными конструктору класса.Аргумент
enforce_csrf_checksможет использоваться для проверки защиты от CSRF (см. выше).Аргумент
json_encoderпозволяет задать пользовательский кодировщик JSON для сериализации JSON, описанной вpost().Аргумент
raise_request_exceptionпозволяет управлять тем, будут ли исключения, возникающие во время запроса, также возбуждаться в тесте. По умолчанию значениеTrue.Новое в Django 3.0:Был добавлен аргумент
raise_request_exception.После получения экземпляра
Client, вы можете вызвать любой из следующих методов:-
get(path, data=None, follow=False, secure=False, **extra) -
Выполняет GET-запрос к указанному
pathи возвращает объектResponse, документация которого приведена ниже.Ключево-значительные пары в словаре
dataиспользуются для создания полезной нагрузки GET. Например:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})…приведёт к выполнению GET-запроса, эквивалентного:
/customers/details/?name=fred&age=7
Именованные аргументы
extraмогут использоваться для задания заголовков, которые будут отправлены в запросе. Например:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_ACCEPT='application/json')…отправит HTTP-заголовок
HTTP_ACCEPTв представление деталей, что является хорошим способом проверки кодовых путей, которые используют методdjango.http.HttpRequest.accepts().Спецификация CGI
Заголовки, отправленные через
**extra, должны соответствовать спецификации CGI. Например, эмуляция другого заголовка «Host», отправленного в HTTP-запросе от браузера к серверу, должна быть передана какHTTP_HOST.Если у вас уже есть аргументы 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-запрос.
-
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **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в заголовке HTTPContent-Type.Если вы не укажете значение для
content_type, значения вdataбудут переданы с типом контента multipart/form-data. В этом случае ключево-значительные пары вdataбудут закодированы как многочастное сообщение и использованы для создания полезной нагрузки POST.Чтобы отправить несколько значений для заданного ключа — например, чтобы указать выбранные значения для
<select multiple>— предоставьте значения как список или кортеж для требуемого ключа. Например, это значениеdataотправит три выбранных значения для поля с именемchoices:{'choices': ('a', 'b', 'd')}Загрузка файлов — это особый случай. Чтобы загрузить файл, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:
>>> c = Client() >>> with open('wishlist.doc') as fp: ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})(Имя
attachmentздесь не имеет значения; используйте то имя, которое ожидает ваш код обработки файлов.)Вы также можете предоставить любой подобный файлу объект (например,
StringIOилиBytesIO) как дескриптор файла. Если вы загружаете вImageField, объект должен иметь атрибутname, который передает валидаторvalidate_image_file_extension. Например:>>> from io import BytesIO >>> img = BytesIO(b'mybinarydata') >>> img.name = 'myimage.jpg'
Обратите внимание, что если вы хотите использовать тот же дескриптор файла для нескольких вызовов
post(), вам нужно будет вручную сбросить указатель файла между отправками. Самый простой способ сделать это — вручную закрыть файл после того, как он был предоставлен методуpost(), как показано выше.Также следует убедиться, что файл открыт таким образом, чтобы данные можно было прочитать. Если ваш файл содержит двоичные данные, такие как изображение, это означает, что вам нужно открыть файл в режиме
rb(чтение двоичных данных).Аргумент
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-запрос.
-
head(path, data=None, follow=False, secure=False, **extra) -
Выполняет HEAD-запрос к указанному
pathи возвращает объектResponse. Этот метод работает так же, какClient.get(), включая аргументыfollow,secureиextra, за исключением того, что он не возвращает тело сообщения.
-
options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) -
Выполняет OPTIONS-запрос к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Если
dataуказано, оно используется в качестве тела запроса, и заголовокContent-Typeустанавливается вcontent_type.Аргументы
follow,secureиextraработают так же, как и дляClient.get().
-
put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) -
Выполняет PUT-запрос к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Если
dataуказано, оно используется в качестве тела запроса, и заголовокContent-Typeустанавливается вcontent_type.Аргументы
follow,secureиextraработают так же, как и дляClient.get().
-
patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) -
Выполняет PATCH-запрос к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Аргументы
follow,secureиextraработают так же, как и дляClient.get().
-
-
delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) -
Выполняет запрос DELETE к предоставленному
pathи возвращает объектResponse. Полезно для тестирования RESTful интерфейсов.Если предоставлен
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается в значениеcontent_type.Аргументы
follow,secureиextraдействуют так же, как и дляClient.get().
-
trace(path, follow=False, secure=False, **extra) -
Выполняет запрос TRACE к предоставленному
pathи возвращает объектResponse. Полезно для имитации диагностических запросов.В отличие от других методов запросов,
dataне предоставляется в качестве ключевого параметра, чтобы соответствовать RFC 7231#section-4.3.8, который предписывает, что запросы TRACE не должны иметь тела.Аргументы
follow,secureиextraдействуют так же, как и дляClient.get().
-
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(который должен быть строкой с точечной записью 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 -
Новое в Django 3.0.
Кортеж из трёх значений, который предоставляет информацию об обработанной исключении, если таковое имело место во время обработки представления.
Значения соответствуют тем, которые возвращает Python
sys.exc_info(). Их значения:- type: Тип исключения.
- value: Экземпляр исключения.
- traceback: Объект отладки, который описывает стек вызовов в момент возникновения исключения.
Если исключений не было, то
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 be compared by name, as the functions # generated by as_view() won't be equal self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__)
Если заданный URL не найден, обращение к этому атрибуту вызовет исключение
Resolver404.
-
Вы также можете использовать синтаксис словарей для объекта ответа, чтобы получить значение настроек в HTTP-заголовках. Например, вы можете определить тип содержимого ответа, используя response['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).
Тестовый клиент имеет два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам как часть условия теста.
-
Объект 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.")
или включив в запрос HTTP-заголовок Accept-Language:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробная информация в Как 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.")
Более подробная информация в Явно устанавливаем активный язык.
Пример
Следующий пример представляет собой тест единицы, использующий тестовый клиент:
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 предоставляет несколько расширений этого базового класса:
Вы можете преобразовать обычный 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(), чтобы избежать этого.
Реализован метод debug() для запуска теста без сбора результатов и перехвата исключений.
TransactionTestCase
-
class TransactionTestCase
TransactionTestCase наследуется от SimpleTestCase, чтобы добавить некоторые функции, специфичные для базы данных:
- Сброс базы данных до известного состояния в начале каждого теста для облегчения тестирования и использования ORM.
- Фикстуры базы данных
fixtures. - Пропуск тестов на основе функций бэкенда базы данных skipping-tests.
- Остальные специализированные методы
assert*.
Класс TestCase Django — это более часто используемый подкласс TransactionTestCase, который использует средства транзакций базы данных для ускорения процесса сброса базы данных до известного состояния в начале каждого теста. Однако следствием этого является то, что некоторые поведения базы данных нельзя протестировать внутри класса Django TestCase. Например, вы не можете протестировать, что блок кода выполняется в рамках транзакции, как это требуется при использовании select_for_update(). В таких случаях следует использовать TransactionTestCase.
TransactionTestCase и TestCase идентичны, за исключением способа сброса базы данных до известного состояния и возможности кода теста проверять эффекты commit и rollback:
TransactionTestCaseсбрасывает базу данных после выполнения теста, обнуляя все таблицы.TransactionTestCaseможет вызывать commit и rollback и наблюдать за эффектами этих вызовов на базе данных.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(), в методах тестов. Изменения в объектах в оперативной памяти, выполненные на уровне класса, сохранятся между методами тестов. Если вам нужно их изменить, вы можете перезагрузить их в методеsetUp()с помощьюrefresh_from_db(), например.
LiveServerTestCase
-
class LiveServerTestCase
LiveServerTestCase выполняет в основном то же самое, что и TransactionTestCase, но с одной дополнительной функцией: он запускает активный сервер Django в фоновом режиме при настройке и выключает его при завершении. Это позволяет использовать автоматизированных клиентов тестов, отличных от стандартного клиента Django, таких как, например, клиент 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.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('%s%s' % (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. См. документациюloaddataдля получения дополнительной информации о определении и установке фикстур.
По соображениям производительности, 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()
В случае, если вы хотите переопределить настройку для метода теста, 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()
Аналогично, 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 | Стандартный перевод и загруженные переводы |
| MEDIA_ROOT, DEFAULT_FILE_STORAGE | Стандартное хранилище файлов |
Очистка тестового ящика выходящей почты
Если вы используете любой из пользовательских классов 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(response, form, field, errors, msg_prefix='') -
Утверждает, что поле на форме вызывает предоставленный список ошибок, когда оно отображается на форме.
form— имя, данное экземпляруFormв контексте шаблона.field— имя поля на форме, которое нужно проверить. Еслиfieldимеет значениеNone, будут проверены ошибки, не относящиеся к полям (ошибки, которые можно получить черезform.non_field_errors()).errors— строка ошибки или список строк ошибок, ожидаемых в результате проверки формы.
-
SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='') -
Утверждает, что
formsetвызывает предоставленный список ошибок при отображении.formset— имя, данное экземпляруFormsetв контексте шаблона.form_index— номер формы вFormset. Еслиform_indexимеет значениеNone, будут проверены ошибки, не относящиеся к форме (ошибки, которые можно получить черезformset.non_form_errors()).field— имя поля на форме, которое нужно проверить. Еслиfieldимеет значениеNone, будут проверены ошибки, не относящиеся к полям (ошибки, которые можно получить черезform.non_field_errors()).errors— строка ошибки или список строк ошибок, ожидаемых в результате проверки формы.
-
SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False) -
Утверждает, что экземпляр
Responseсоздал указаннуюstatus_codeи чтоtextприсутствует в содержимом ответа. Если предоставлен параметрcount,textдолжно встречаться ровноcountраз в ответе.Установите
htmlвTrue, чтобы обработатьtextкак HTML. Сравнение с содержимым ответа будет основано на HTML-семантике вместо точного соответствия символа за символом. Пробелы игнорируются в большинстве случаев, порядок атрибутов не важен. См.assertHTMLEqual()для более подробной информации.
-
SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False) -
Утверждает, что экземпляр
Responseсоздал указаннуюstatus_codeи чтоtextне присутствует в содержимом ответа.Установите
htmlвTrue, чтобы обработатьtextкак HTML. Сравнение с содержимым ответа будет основано на HTML-семантике вместо точного соответствия символа за символом. Пробелы игнорируются в большинстве случаев, порядок атрибутов не важен. См.assertHTMLEqual()для более подробной информации.
-
SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None) -
Утверждает, что шаблон с заданным именем использовался при рендеринге ответа.
Имя — строка, например,
'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) -
Утверждает, что ответ вернул статус перенаправления
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-элемента не важен.
- Атрибуты без аргумента равны атрибутам, которые равны по имени и значению (см. примеры).
- Текст, символьные ссылки и ссылки на сущности, которые ссылаются на один и тот же символ, эквивалентны.
Следующие примеры являются допустимыми тестами и не вызывают никаких
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.Пробелы в большинстве случаев игнорируются, а порядок атрибутов не имеет значения. Передаваемые аргументы должны быть корректными фрагментами HTML.
-
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=repr, ordered=True, msg=None) -
Утверждает, что запрос
qsвозвращает определенный список значенийvalues.Сравнение содержимого
qsиvaluesвыполняется с применениемtransformкqs. По умолчанию это означает, чтоrepr()каждого значения вqsсравнивается сvalues. Еслиrepr()не предоставляет уникального или полезного сравнения, можно использовать любой другой вызываемый объект.По умолчанию сравнение также зависит от порядка. Если
qsне предоставляет неявный порядок, вы можете установить параметрorderedвFalse, что превращает сравнение в сравнениеcollections.Counter. Если порядок не определен (если заданныйqsне упорядочен и сравнение выполняется с несколькими упорядоченными значениями), генерируетсяValueError.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
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 в любом тесте.
AsyncClient имеет те же методы и сигнатуры, что и синхронный (обычный) клиент, за двумя исключениями:
- Параметр
followне поддерживается. -
Заголовки, переданные в качестве аргументов ключевых слов
extra, не должны иметь префиксHTTP_, необходимый синхронному клиенту (см.Client.get()). Например, вот как установить заголовок HTTPAccept:>>> c = AsyncClient() >>> c.get( ... '/customers/details/', ... {'name': 'fred', 'age': 7}, ... ACCEPT='application/json' ... )
Используя 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.BaseDatabaseFeatures класс для получения полного списка функций базы данных, которые могут использоваться в качестве основы для пропуска тестов.
-
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/3.1/topics/testing/tools/