Инструменты тестирования
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.После получения экземпляра
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в HTTP-заголовкеContent-Type.Если вы не предоставите значение для
content_type, значения вdataбудут переданы с типом содержимого multipart/form-data. В этом случае ключевые пары вdataбудут закодированы как сообщение multipart и использованы для создания полезной нагрузки данных POST.Для отправки нескольких значений для заданного ключа — например, для указания выбранных значений для
<select multiple>— предоставьте значения в виде списка или кортежа для требуемого ключа. Например, это значениеdataотправит три выбранных значения для поля с именемchoices:{'choices': ('a', 'b', 'd')}Отправка файлов — это особый случай. Чтобы отправить файл POST, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:
>>> c = Client() >>> with open('wishlist.doc', 'rb') 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будет установлен в значение аргументаbackend(который должен быть строкой пути Python с точками), или вsettings.AUTHENTICATION_BACKENDS[0]если значение не предоставлено. Функцияauthenticate(), вызываемая методомlogin(), обычно аннотирует пользователя таким образом.Этот метод быстрее, чем
login(), так как дорогостоящие алгоритмы хеширования паролей пропускаются. Кроме того, вы можете ускоритьlogin()с помощью использования более слабого хешера во время тестирования.
-
logout() -
Если ваш сайт использует систему аутентификации Django, метод
logout()может использоваться для моделирования эффекта выхода пользователя из вашего сайта.После вызова этого метода, тестовый клиент очистит все куки и данные сессии до значений по умолчанию. Последующие запросы будут исходить от
AnonymousUser.
-
Тестирование ответов
Методы get() и post() оба возвращают объект Response. Этот объект Response не совпадает с объектом HttpResponse, возвращаемым представлениями Django; объект тестового ответа содержит дополнительные данные, полезные для кода тестов для проверки.
В частности, объект Response имеет следующие атрибуты:
-
class Response -
-
client -
Тестовый клиент, который был использован для отправки запроса, приведшего к получению ответа.
-
content -
Тело ответа в виде байтовой строки. Это конечное содержимое страницы, отрисованное представлением, или сообщение об ошибке.
-
context -
Экземпляр шаблона
Context, который использовался для рендеринга шаблона, породившего содержимое ответа.Если отрисованная страница использовала несколько шаблонов, то
contextбудет списком объектовContext, в порядке их рендеринга.Независимо от количества используемых шаблонов при рендеринге, вы можете получить значения контекста, используя оператор
[]. Например, переменная контекстаnameможет быть получена так:>>> response = client.get('/foo/') >>> response.context['name'] 'Arthur'Не используете Django шаблоны?
Этот атрибут заполняется только при использовании бэкенда
DjangoTemplates. Если вы используете другой движок шаблонов,context_dataможет быть подходящей альтернативой для ответов с этим атрибутом.
-
exc_info -
Кортеж из трех значений, предоставляющий информацию об необработанной ошибке, если таковая произошла во время работы представления.
Значения соответствуют (тип, значение, отслеживание стека), как возвращаемые функцией Python
sys.exc_info(). Их значения:- тип: Тип ошибки.
- значение: Экземпляр ошибки.
- отслеживание стека: Объект отслеживания стека, который описывает стек вызовов в момент, когда ошибка произошла.
Если ошибка не произошла, то
exc_infoбудетNone.
-
json(**kwargs) -
Тело ответа, разобранное как JSON. Дополнительные ключевые аргументы передаются функции
json.loads(). Например:>>> response = client.get('/foo/') >>> response.json()['name'] 'Arthur'Если заголовок
Content-Typeне"application/json", то при попытке парсинга ответа будет поднята ошибкаValueError.
-
request -
Данные запроса, которые стимулировали ответ.
-
wsgi_request -
Объект
WSGIRequest, сгенерированный обработчиком теста, который сгенерировал ответ.
-
status_code -
HTTP статус ответа в виде целого числа. Полный список кодов см. в реестре кодов статуса IANA.
-
templates -
Список объектов
Template, используемых для рендеринга конечного содержимого в порядке их рендеринга. Для каждого шаблона в списке используйтеtemplate.nameдля получения имени файла шаблона, если шаблон был загружен из файла. (Имя — строка, например,'admin/index.html'.)Не используете Django шаблоны?
Этот атрибут заполняется только при использовании бэкенда
DjangoTemplates. Если вы используете другой движок шаблонов,template_nameможет быть подходящей альтернативой, если вам нужно только имя шаблона, используемого для рендеринга.
-
resolver_match -
Экземпляр
ResolverMatchдля ответа. Вы можете использовать атрибутfunc, например, для проверки представления, которое обслужило ответ:# my_view here is a function based view self.assertEqual(response.resolver_match.func, my_view) # class-based views need to 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.
-
Как и в обычном ответе, вы также можете получить доступ к заголовкам через HttpResponse.headers. Например, вы можете определить тип содержимого ответа, используя response.headers['Content-Type'].
Исключения
Если вы направите тестовый клиент на представление, которое вызывает исключение, и Client.raise_request_exception равно True, эта ошибка будет видна в тестовом случае. Затем вы можете использовать стандартный try ... except блок или assertRaises() для проверки исключений.
Единственными исключениями, которые не видны тестовому клиенту, являются Http404, PermissionDenied, SystemExit и SuspiciousOperation. Django обрабатывает эти исключения внутри и преобразует их в соответствующие коды HTTP-ответов. В этих случаях вы можете проверить response.status_code в своем тесте.
Если Client.raise_request_exception равно False, тестовый клиент вернёт ответ 500, как и возвращается браузеру. У ответа есть атрибут exc_info для предоставления информации об необработанной ошибке.
Состояние
Тестовый клиент имеет состояние. Если ответ возвращает cookie, то эта cookie будет сохранена в тестовом клиенте и отправлена со всеми последующими запросами get() и post().
Политики истечения срока действия этих cookies не соблюдаются. Если вам нужно, чтобы cookie истекла, удалите её вручную или создайте новый экземпляр Client (что фактически удалит все cookies).
У тестового клиента есть два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Объект 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, который добавляет эту функциональность:
- Некоторые полезные утверждения, такие как:
- Проверка вызова callable
raises a certain exception. - Проверка вызова callable
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на равенство.
- Проверка вызова callable
- Возможность запуска тестов с изменёнными настройками.
- Использование
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. - Пропуск тестов на основе возможностей бэкенда базы данных.
- Остальные специализированные методы
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()будет вызываться перед каждым тестом, что сведет на нет преимущества скорости.Изменено в Django 3.2:Объекты, назначенные атрибутам класса в
setUpTestData(), должны поддерживать создание глубоких копий с помощьюcopy.deepcopy(), чтобы изолировать их от изменений, выполняемых методами каждого теста. В предыдущих версиях Django эти объекты повторно использовались, и изменения, внесенные в них, сохранялись между методами тестов.
-
classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False) -
Новое в Django 3.2.
Возвращает менеджер контекста, который захватывает
transaction.on_commit()обратные вызовы для заданного соединения с базой данных. Он возвращает список, который содержит, по завершении контекста, захваченные функции обратного вызова. Из этого списка вы можете сделать утверждения об обратных вызовах или вызвать их, чтобы вызвать их побочные эффекты, имитируя подтверждение.using— это псевдоним соединения с базой данных для захвата обратных вызовов.Если
executeравноTrue, все обратные вызовы будут вызваны по выходу из менеджера контекста, если не возникло исключение. Это имитирует подтверждение после заключённого в блок кода.Например:
from django.core import mail from django.test import TestCase class ContactTests(TestCase): def test_post(self): with self.captureOnCommitCallbacks(execute=True) as callbacks: response = self.client.post( '/contact/', {'message': 'I like your site'}, ) self.assertEqual(response.status_code, 200) self.assertEqual(len(callbacks), 1) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Contact Form') self.assertEqual(mail.outbox[0].body, 'I like your site')
LiveServerTestCase
-
class LiveServerTestCase
LiveServerTestCase делает по существу то же самое, что и TransactionTestCase, но с одной дополнительной функцией: он запускает активный сервер Django на заднем плане при настройке и останавливает его при разборке. Это позволяет использовать автоматизированных тестовых клиентов, отличных от Django-клиента, таких как, например, клиент 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вхождений.Пробелы в большинстве случаев игнорируются, а порядок атрибутов не имеет значения. См.
assertHTMLEqual()для получения более подробной информации.
-
SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) -
Утверждает, что JSON-фрагменты
rawиexpected_dataравны. Применяются стандартные правила JSON для игнорирования пробелов, так как тяжёлая работа делегирована библиотекеjson.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None) -
Утверждает, что JSON-фрагменты
rawиexpected_dataне равны. См.assertJSONEqual()для получения дополнительной информации.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
TransactionTestCase.assertQuerysetEqual(qs, values, transform=None, ordered=True, msg=None) -
Утверждает, что набор запросов
qsсоответствует определённому итерируемому набору значенийvalues.Если
transformпредоставлен,valuesсравнивается со списком, полученным путём примененияtransformк каждому членуqs.По умолчанию сравнение также зависит от порядка. Если
qsне предоставляет явного порядка, вы можете установить параметрorderedвFalse, что превратит сравнение в сравнение поcollections.Counter. Если порядок неопределён (если предоставленныйqsне упорядочен и сравнение выполняется с несколькими упорядоченными значениями), то возбуждаетсяValueError.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.Изменено в Django 3.2:Значение по умолчанию аргумента
transformбыло изменено наNone.Добавлено в Django 3.2:Добавлена поддержка прямого сравнения наборов запросов.
Устаревшее начиная с версии 3.2: Если
transformне предоставлен, аvaluesявляется списком строк, он сравнивается со списком, полученным путём примененияrepr()к каждому членуqs. Это поведение устарело и будет удалено в Django 4.1. Если вам необходимо это, явно установитеtransformвrepr.
-
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, вам нужно учитывать несколько моментов.
Во-первых, ваши тесты должны быть 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.2/topics/testing/tools/