Инструменты тестирования
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-адресов клиент тестирования использует URLconf, на который указывает ваше значение
ROOT_URLCONF. -
Хотя приведенный выше пример будет работать в интерактивном интерпретаторе Python, некоторые возможности клиента тестирования, особенно связанные с шаблонами, доступны только во время выполнения тестов.
Причина в том, что инструмент запуска тестов Django выполняет некоторую «магию», чтобы определить, какой шаблон был загружен данным представлением. Эта «магия» (по существу, подмена системы шаблонов Django в памяти) происходит только во время запуска тестов.
-
По умолчанию клиент тестирования отключит любые проверки CSRF, выполняемые вашим сайтом.
Если по какой-то причине вы хотите, чтобы клиент тестирования выполнял проверки CSRF, вы можете создать экземпляр клиента тестирования, который выполняет проверки CSRF. Для этого передайте аргумент
enforce_csrf_checksпри создании вашего клиента:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Выполнение запросов
Используйте класс django.test.Client для выполнения запросов.
-
class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults) -
Он не требует аргументов при создании. Однако вы можете использовать ключевые аргументы для задания некоторых стандартных заголовков. Например, это отправит заголовок
User-Agentв каждом запросе:>>> 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_X_REQUESTED_WITH='XMLHttpRequest')…отправит HTTP-заголовок
HTTP_X_REQUESTED_WITHк странице деталей, что является хорошим способом проверки кодовых путей, использующих методdjango.http.HttpRequest.is_ajax().Спецификация 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().Изменено в Django 2.2:Сериализация JSON была расширена для поддержки списков и кортежей. В более ранних версиях сериализовались только словари.
Если вы укажете любой другой
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')}Отправка файлов — это особый случай. Чтобы отправить файл, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:
>>> 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), либо с помощью тестовой фикстуры. Помните, что если вы хотите, чтобы у вашего тестового пользователя был пароль, вы не можете установить пароль пользователя, установив атрибут пароля напрямую — вы должны использовать функцию
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(). Их значения:- тип: Тип исключения.
- значение: Экземпляр исключения.
- трассировка: Объект трассировки, который содержит стек вызовов в момент возникновения исключения.
Если исключение не возникло, то
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.")
или включив заголовок Accept-Language HTTP в запросе:
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 -
Новое в Django 2.2.
SimpleTestCaseпо умолчанию запрещает запросы к базе данных. Это помогает избежать выполнения записывающих запросов, которые повлияют на другие тесты, поскольку каждыйSimpleTestCaseтест не выполняется в транзакции. Если вас это не беспокоит, вы можете отключить это поведение, установив атрибут классаdatabasesв'__all__'в вашем классе тестов.
-
SimpleTestCase.allow_database_queries -
Устарело начиная с версии 2.2.
Этот атрибут устарел в пользу
databases. Предыдущее поведениеallow_database_queries = Trueможет быть достигнуто установкой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 идентичны, за исключением способа сброса базы данных до известного состояния и возможности тестирования эффектов 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 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.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. Этот клиент создается заново для каждого теста, поэтому вам не нужно беспокоиться о состоянии (например, о cookie), которое переносится от одного теста к другому.
Это означает, что вместо создания экземпляра Client в каждом тесте:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
…вы можете обратиться к self.client, как показано ниже:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
Настройка клиента тестов
-
SimpleTestCase.client_class
Если вы хотите использовать другой класс Client (например, подкласс с настраиваемым поведением), используйте атрибут класса client_class:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Загрузка фикстур
-
TransactionTestCase.fixtures
Тестовый случай для веб-сайта на базе данных мало пригоден, если в базе данных нет данных. Тесты более читабельны и их легче поддерживать, создавая объекты с помощью ORM, например, в TestCase.setUpTestData(), однако вы также можете использовать фикстуры.
Фикстура — это набор данных, который Django знает, как импортировать в базу данных. Например, если ваш сайт имеет учетные записи пользователей, вы можете создать фикстуру фиктивных учетных записей пользователей, чтобы заполнить свою базу данных во время тестирования.
Самый простой способ создания фикстуры — использовать команду manage.py dumpdata. Это предполагает, что в вашей базе данных уже есть некоторые данные. См. dumpdata
documentation для получения дополнительной информации.
После создания фикстуры и размещения её в каталоге fixtures в одном из ваших INSTALLED_APPS, вы можете использовать её в своих модульных тестах, указав атрибут класса fixtures в вашем подклассе django.test.TestCase:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Вот что конкретно произойдет:
- В начале каждого теста, перед выполнением
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, будут генерировать ошибки утверждения для предотвращения утечки состояния между тестами.
-
TransactionTestCase.multi_db
Устарело начиная с версии 2.2.
Этот атрибут устарел в пользу databases. Предыдущее поведение multi_db = True можно получить, установив databases = '__all__'.
-
TestCase.databases
По умолчанию только база данных default будет обернута транзакцией во время выполнения TestCase, и попытки запроса к другим базам данных приведут к ошибкам утверждения для предотвращения утечки состояния между тестами.
Используйте класс-атрибут databases в тестовом классе, чтобы запросить обертывание транзакции для баз данных, отличных от default.
Например:
class OtherDBTests(TestCase):
databases = {'other'}
def test_other_db_query(self):
...
Этот тест позволит выполнять запросы только к базе данных other. Так же, как и для SimpleTestCase.databases и TransactionTestCase.databases, константа '__all__' может быть использована для указания того, что тест должен разрешать запросы ко всем базам данных.
-
TestCase.multi_db
Устарело начиная с версии 2.2.
Этот атрибут устарел в пользу databases. Предыдущее поведение multi_db = True можно получить, установив 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='') -
Новая функция в Django 2.2.
Утверждает, что два 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
Вы также можете исключать тесты по тегу. Чтобы запустить тесты ядра, если они не медленные:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag имеет приоритет над test --tag, поэтому если у теста две метки, и вы выбираете одну из них, а исключаете другую, тест не будет запущен.
Сервисы электронной почты
Если какие-либо ваши 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.0/topics/testing/tools/