Инструменты тестирования
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 в закодированном формате, вы можете использовать эту кодировку вместо использования аргумента 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')}Отправка файлов является особым случаем. Для отправки файла вам нужно указать только имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, как значение. Например, если у вашей формы есть поля
nameиattachment, последнее —FileField:>>> c = Client() >>> with open('wishlist.doc', 'rb') as fp: ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})Вы также можете указать любой объект типа файл (например,
StringIOилиBytesIO) как дескриптор файла. Если вы загружаете файл вImageField, объект должен иметь атрибутname, передающий валидаторvalidate_image_file_extension. Например:>>> from io import BytesIO >>> img = BytesIO(b'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’s
sys.exc_info(). Их значения:- type: Тип исключения.
- value: Экземпляр исключения.
- traceback: Объект трассировки, который содержит стек вызовов в момент возникновения исключения.
Если исключение не произошло, то
exc_infoбудетNone.
-
json(**kwargs) -
Тело ответа, разобранное как JSON. Дополнительные ключевые аргументы передаются в
json.loads(). Например:>>> response = client.get('/foo/') >>> response.json()['name'] 'Arthur'Если заголовок
Content-Typeне"application/json", то будет поднято исключениеValueErrorпри попытке разобрать ответ.
-
request -
Данные запроса, которые стимулировали ответ.
-
wsgi_request -
Экземпляр
WSGIRequest, сгенерированный обработчиком теста, который сгенерировал ответ.
-
status_code -
HTTP статус ответа, как целое число. Полный список кодов см. в реестре кодов состояния IANA.
-
templates -
Список экземпляров
Template, используемых для рендеринга конечного содержимого, в порядке их рендеринга. Для каждого шаблона в списке используйтеtemplate.nameдля получения имени файла шаблона, если шаблон был загружен из файла. (Имя — строка, например,'admin/index.html'.)Не используете Django-шаблоны?
Этот атрибут заполняется только при использовании бэкенда
DjangoTemplates. Если вы используете другой движок шаблонов,template_nameможет быть подходящей альтернативой, если вам нужно только имя шаблона, используемого для рендеринга.
-
resolver_match -
Экземпляр
ResolverMatchдля ответа. Вы можете использовать атрибутfunc, например, для проверки представления, которое обслужило ответ:# my_view here is a function based view. self.assertEqual(response.resolver_match.func, my_view) # Class-based views need to compare the view_class, as the # functions generated by as_view() won't be equal. self.assertIs(response.resolver_match.func.view_class, MyView)
Если указанный URL не найден, обращение к этому атрибуту вызовет исключение
Resolver404.
-
Как и в обычном ответе, вы также можете получить доступ к заголовкам через HttpResponse.headers. Например, вы можете определить тип содержимого ответа, используя response.headers['Content-Type'].
Исключения
Если вы направляете тестового клиента на представление, которое поднимает исключение, и Client.raise_request_exception равно True, это исключение будет видно в тестовом случае. Тогда вы можете использовать стандартный блок try ... except или assertRaises() для проверки на исключения.
Единственные исключения, которые не видны тестовому клиенту, это Http404, PermissionDenied, SystemExit и SuspiciousOperation. Django перехватывает эти исключения внутренне и преобразует их в соответствующие коды HTTP-ответов. В этих случаях вы можете проверить response.status_code в своём тесте.
Если Client.raise_request_exception равно False, тестовый клиент вернёт ответ 500, как было бы возвращено браузеру. Ответ имеет атрибут exc_info для предоставления информации об обработанном исключении.
Состояние
Тестовый клиент имеет состояние. Если ответ возвращает cookie, то эта cookie хранится в тестовом клиенте и отправляется со всеми последующими запросами get() и post().
Политики истечения срока действия этих cookie не соблюдаются. Если вы хотите, чтобы cookie истекла, удалите её вручную или создайте новый экземпляр Client (что фактически удалит все cookie).
Тестовый клиент имеет атрибуты, которые хранят информацию о состоянии.
-
Client.cookies -
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. См. документацию модуляhttp.cookiesдля получения дополнительной информации.
-
Client.session -
Объект, подобный словарю, содержащий информацию о сессии. См. документацию по сессиям для получения полной информации.
Чтобы изменить сессию и сохранить её, её необходимо сначала сохранить в переменной (потому что каждый раз при доступе к этому свойству создаётся новый
SessionStore):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка
При тестировании приложений, которые поддерживают интернационализацию и локализация, вам может потребоваться установить язык для запроса тестового клиента. Способ зависит от того, включён ли LocaleMiddleware.
Если middleware включён, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или добавив заголовок Accept-Language в запрос:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Как Django определяет предпочтение языка.
Если middleware не включён, активный язык может быть установлен с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Явное назначение активного языка.
Пример
Следующее — это юнит-тест с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См. также
Предоставленные классы тестовых случаев
Обычные классы юнит-тестов Python наследуются от базового класса unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Вы можете преобразовать обычный unittest.TestCase в любой из подклассов: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные возможности тестирования Python будут доступны, и они будут дополнены некоторыми полезными дополнениями, как описано в каждом разделе ниже.
SimpleTestCase
-
class SimpleTestCase
Подкласс unittest.TestCase, который добавляет эту функциональность:
- Некоторые полезные утверждения, такие как:
- Проверка, что вызываемый метод
raises a certain exception. - Проверка, что вызываемый метод
triggers a certain warning. - Проверка поля формы
rendering and error treatment. - Проверка
HTML responses for the presence/lack of a given fragment. - Проверка того, что шаблон
has/hasn't been used to generate a given response contentиспользуется. - Проверка того, что два
URLsравны. - Проверка, что приложение выполняет перенаправление HTTP
redirect. - Надежная проверка двух
HTML fragmentsна равенство/неравенство илиcontainment. - Надежная проверка двух
XML fragmentsна равенство/неравенство. - Надежная проверка двух
JSON fragmentsна равенство.
- Проверка, что вызываемый метод
- Возможность запуска тестов с измененными настройками.
- Использование
clientClient.
Если ваши тесты выполняют запросы к базе данных, используйте подклассы TransactionTestCase или TestCase.
-
SimpleTestCase.databases -
SimpleTestCaseпо умолчанию запрещает запросы к базе данных. Это помогает избежать выполнения записывающих запросов, которые повлияют на другие тесты, так как каждыйSimpleTestCaseтест не выполняется в транзакции. Если вас не беспокоит эта проблема, вы можете отключить это поведение, установивdatabasesатрибут класса на'__all__'в вашем тестовом классе.
Предупреждение
SimpleTestCase и его подклассы (например, TestCase, …) полагаются на setUpClass() и tearDownClass() для выполнения некоторой инициализации на уровне класса (например, переопределение настроек). Если вам нужно переопределить эти методы, не забудьте вызвать реализацию super:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
Учитывайте поведение Python, если во время setUpClass() возникает исключение. В этом случае ни тесты в классе, ни tearDownClass() не выполняются. В случае с django.test.TestCase это приведет к утечке транзакции, созданной в super(), что приводит к различным симптомам, включая сбой сегментации на некоторых платформах (сообщалось о macOS). Если вы хотите намеренно вызвать исключение, например, unittest.SkipTest в setUpClass(), убедитесь, что вы сделали это до вызова super() , чтобы избежать этого.
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(), должны поддерживать создание глубоких копий с помощьюcopy.deepcopy()для изоляции их от изменений, производимых методами каждого теста.
-
classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False) -
Возвращает менеджер контекста, который захватывает вызовы
transaction.on_commit()для заданного подключения к базе данных. Он возвращает список, который содержит, по завершении контекста, захваченные функции обратного вызова. Из этого списка вы можете сделать утверждения о вызовах или вызвать их, чтобы вызвать их побочные эффекты, моделируя подтверждение.using— псевдоним подключения к базе данных для захвата обратных вызовов.Если
executeравноTrue, все обратные вызовы будут вызваны по завершении менеджера контекста, если не произошла ошибка. Это моделирует подтверждение после обернутого блока кода.Например:
from django.core import mail from django.test import TestCase class ContactTests(TestCase): def test_post(self): with self.captureOnCommitCallbacks(execute=True) as callbacks: response = self.client.post( '/contact/', {'message': 'I like your site'}, ) self.assertEqual(response.status_code, 200) self.assertEqual(len(callbacks), 1) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Contact Form') self.assertEqual(mail.outbox[0].body, 'I like your site')Изменено в Django 4.0:В более старых версиях новые обратные вызовы, добавленные во время выполнения
transaction.on_commit()обратных вызовов, не захватывались.
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.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%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 , приведут к ошибке утверждения, чтобы предотвратить утечку состояния между тестами.
-
TestCase.databases
По умолчанию только база данных default будет обернута транзакцией во время выполнения TestCase, и попытки запросить другие базы данных приведут к ошибкам утверждения, чтобы предотвратить утечку состояния между тестами.
Используйте атрибут класса databases в тестовом классе, чтобы запросить обертывание транзакцией для баз данных, не являющихся default.
Например:
class OtherDBTests(TestCase):
databases = {'other'}
def test_other_db_query(self):
...
Этот тест позволит выполнять запросы только к базе данных other. Так же, как и для SimpleTestCase.databases и TransactionTestCase.databases, константа '__all__' может быть использована для указания того, что тест должен разрешить запросы ко всем базам данных.
Переопределение настроек
Предупреждение
Используйте приведенные ниже функции для временного изменения значения настроек в тестах. Не манипулируйте django.conf.settings напрямую, так как Django не восстановит исходные значения после таких манипуляций.
-
SimpleTestCase.settings()
Для целей тестирования часто бывает полезно временно изменить настройку и вернуться к исходному значению после выполнения тестового кода. Для этого случая Django предоставляет стандартный менеджер контекста Python (см. PEP 343), называемый settings(), который можно использовать следующим образом:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Этот пример переопределит настройку LOGIN_URL для кода в блоке with и восстановит ее значение в исходное состояние после этого.
-
SimpleTestCase.modify_settings()
Переопределять настройки, содержащие список значений, может оказаться неудобно. На практике часто достаточно добавлять или удалять значения. Django предоставляет менеджер контекста modify_settings() для более удобного изменения настроек:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
'remove': [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
],
}):
response = self.client.get('/')
# ...
Для каждого действия вы можете указать либо список значений, либо строку. Когда значение уже существует в списке, append и prepend не оказывают никакого влияния, так же как и remove при отсутствии значения.
-
override_settings(**kwargs)
В случае, если вы хотите переопределить настройку для тестового метода, Django предоставляет декоратор override_settings() (см. PEP 318). Он используется следующим образом:
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Декоратор также может быть применен к классам TestCase:
from django.test import TestCase, override_settings
@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
-
modify_settings(*args, **kwargs)
Аналогично, Django предоставляет декоратор modify_settings():
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
def test_cache_middleware(self):
response = self.client.get('/')
# ...
Декоратор также может быть применен к классам тестовых случаев:
from django.test import TestCase, modify_settings
@modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get('/')
# ...
Примечание
Когда используется класс, эти декораторы изменяют класс напрямую и возвращают его; они не создают и не возвращают измененную копию. Таким образом, если вы попытаетесь изменить приведенные выше примеры, чтобы присвоить возвращаемое значение другому имени, отличном от LoginTestCase или MiddlewareTestCase, вы можете удивиться, обнаружив, что исходные классы тестовых случаев по-прежнему одинаково затронуты декоратором. Для данного класса modify_settings() всегда применяется после override_settings().
Предупреждение
Файл настроек содержит некоторые настройки, которые используются только при инициализации внутренних компонентов Django. Если вы изменяете их с помощью override_settings, настройка изменяется, если вы обращаетесь к ней через модуль django.conf.settings, однако внутренние компоненты Django обращаются к ней по-другому. По сути, использование override_settings() или modify_settings() с этими настройками, вероятно, не даст ожидаемого результата.
Мы не рекомендуем изменять настройку DATABASES. Изменение настройки CACHES возможно, но немного сложно, если вы используете внутренние компоненты, которые используют кэширование, такие как django.contrib.sessions. Например, вам придется повторно инициализировать бэкенд сессий в тесте, использующем кэшированные сессии и переопределяющем CACHES.
Наконец, избегайте создания псевдонимов ваших настроек в виде констант уровня модуля, так как override_settings() не будет работать с такими значениями, так как они оцениваются только при первом импорте модуля.
Вы также можете смоделировать отсутствие настройки, удалив ее после переопределения настроек, например:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
При переопределении настроек убедитесь, что обрабатываете случаи, в которых код вашего приложения использует кэш или подобную функцию, которая сохраняет состояние, даже если настройка изменена. Django предоставляет сигнал django.test.signals.setting_changed, который позволяет регистрировать обратные вызовы для очистки и иного сброса состояния при изменении настроек.
Сам Django использует этот сигнал для сброса различных данных:
| Переопределенные настройки | Сброшенные данные |
|---|---|
| USE_TZ, TIME_ZONE | Временная зона баз данных |
| TEMPLATES | Движки шаблонов |
| SERIALIZATION_MODULES | Кэш сериализаторов |
| LOCALE_PATHS, LANGUAGE_CODE | Стандартный перевод и загруженные переводы |
| MEDIA_ROOT, DEFAULT_FILE_STORAGE | Стандартное хранилище файлов |
Изоляция приложений
-
utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None) -
Регистрирует модели, определённые в обёрнутом контексте, в собственном изолированном реестре
apps. Эта функциональность полезна при создании классов моделей для тестов, так как классы будут чисто удалены после этого, и нет риска столкновения имён.Метки приложений, которые должен содержать изолированный реестр, должны быть переданы в качестве отдельных аргументов. Вы можете использовать
isolate_apps()в качестве декоратора или менеджера контекста. Например:from django.db import models from django.test import SimpleTestCase from django.test.utils import isolate_apps class MyModelTests(SimpleTestCase): @isolate_apps("app_label") def test_model_definition(self): class TestModel(models.Model): pass ...… или:
with isolate_apps("app_label"): class TestModel(models.Model): pass ...Форма декоратора также может быть применена к классам.
Можно указать два необязательных ключевых аргумента:
-
attr_name: атрибут, назначенный изолированному реестру, если используется как декоратор класса. -
kwarg_name: ключевой аргумент, передающий изолированный реестр, если используется как декоратор функции.
Временный экземпляр
Apps, используемый для изоляции регистрации моделей, можно получить как атрибут, когда используется как декоратор класса, используя параметрattr_name:@isolate_apps("app_label", attr_name="apps") class TestModelDefinition(SimpleTestCase): def test_model_definition(self): class TestModel(models.Model): pass self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)… или альтернативно, как аргумент в методе теста, при использовании в качестве декоратора метода, используя параметр
kwarg_name:class TestModelDefinition(SimpleTestCase): @isolate_apps("app_label", kwarg_name="apps") def test_model_definition(self, apps): class TestModel(models.Model): pass self.assertIs(apps.get_model("app_label", "TestModel"), TestModel) -
Очистка тестового ящика вывода электронной почты
Если вы используете классы Django с настраиваемыми TestCase классами, тестовый запуск очистит содержимое тестового ящика вывода электронной почты в начале каждого тестового случая.
Для получения более подробной информации об услугах электронной почты во время тестов, см. Услуги электронной почты ниже.
Ассершены
Поскольку стандартный класс Python unittest.TestCase реализует методы утверждений, такие как assertTrue() и assertEqual(), настраиваемый класс Django TestCase предоставляет ряд пользовательских методов утверждений, которые полезны для тестирования веб-приложений:
Сообщения об ошибках, предоставляемые большинством этих методов утверждения, можно настроить с помощью аргумента msg_prefix. Эта строка будет добавлена в начало любого сообщения об ошибке, сгенерированного утверждением. Это позволяет предоставить дополнительные сведения, которые могут помочь определить местоположение и причину сбоя в наборе тестов.
-
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs) -
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message) -
Утверждает, что выполнение
callableвызываетexpected_exceptionи чтоexpected_messageнаходится в сообщении исключения. Любой другой результат сообщается как сбой. Это упрощённая версияunittest.TestCase.assertRaisesRegex()с отличием, чтоexpected_messageне обрабатывается как регулярное выражение.Если указаны только параметры
expected_exceptionиexpected_message, возвращается менеджер контекста, чтобы код, который тестируется, можно было вставить непосредственно, а не как функцию:with self.assertRaisesMessage(ValueError, 'invalid literal for int()'): int('a')
-
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs) -
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message) -
Аналогично
SimpleTestCase.assertRaisesMessage(), но дляassertWarnsRegex()вместоassertRaisesRegex().
-
SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='') -
Утверждает, что поле формы работает корректно с различными входными данными.
Параметры: - fieldclass – класс поля, подлежащего тестированию.
- valid – словарь, сопоставляющий допустимые входные данные с ожидаемыми очищенными значениями.
- invalid – словарь, сопоставляющий недопустимые входные данные с одним или несколькими сообщениями об ошибках.
- field_args – аргументы, передаваемые при создании поля.
- field_kwargs – ключевые аргументы, передаваемые при создании поля.
-
empty_value – ожидаемый очищенный вывод для входных данных в
empty_values.
Например, следующий код проверяет, что
EmailFieldпринимаетa@a.comв качестве допустимого адреса электронной почты, но отклоняетaaaс разумным сообщением об ошибке:self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
-
SimpleTestCase.assertFormError(form, field, errors, msg_prefix='') -
Утверждает, что поле в форме вызывает предоставленный список ошибок.
form— экземплярForm. Форма должна быть связанной, но необязательно валидирована (assertFormError()автоматически вызоветfull_clean()в форме).field— имя поля в форме для проверки. Для проверки ошибок формы, не относящихся к полям, используйтеnon-field errors, используйтеfield=None.errors— список всех ожидаемых сообщений об ошибках для поля. Вы также можете передать одну строку ошибки, если ожидается только одна ошибка, что означает, чтоerrors='error message'то же самое, что иerrors=['error message'].Изменено в Django 4.1:В более ранних версиях использование пустого списка ошибок с
assertFormError()всегда проходило, независимо от того, были ли у поля ошибки или нет. Начиная с Django 4.1, использованиеerrors=[]будет проходить только в том случае, если у поля фактически нет ошибок.Django 4.1 также изменил поведение
assertFormError(), когда у поля несколько ошибок. В более ранних версиях, если у поля было несколько ошибок, и вы проверяли только некоторые из них, тест проходил. Начиная с Django 4.1, список ошибок должен точно соответствовать фактическим ошибкам поля.Устарело начиная с версии 4.1: Поддержка передачи объекта ответа и имени формы в
assertFormError()устарела и будет удалена в Django 5.0. Вместо этого используйте экземпляр формы напрямую.
-
SimpleTestCase.assertFormsetError(formset, form_index, field, errors, msg_prefix='') -
Утверждает, что
formsetвызывает предоставленный список ошибок при отрисовке.formset— экземплярFormset. Форма множества должна быть связанной, но необязательно валидированной (assertFormsetError()автоматически вызоветfull_clean()в наборе форм).form_index— номер формы в множестве форм (начиная с 0). Используйтеform_index=Noneдля проверки ошибок набора форм, не относящихся к форме, т.е. ошибок, которые вы получаете при вызовеformset.non_form_errors(). В этом случае вы также должны использоватьfield=None.fieldиerrorsимеют такое же значение, как параметрыassertFormError().Устарело начиная с версии 4.1: Поддержка передачи объекта ответа и имени набора форм в
assertFormsetError()устарела и будет удалена в Django 5.0. Вместо этого используйте экземпляр набора форм напрямую.
-
SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False) -
Утверждает, что произведённый
responseдал указанный кодstatus_codeи чтоtextпоявляется в егоcontent. Еслиcountпредоставлен,textдолжен встречаться ровноcountраз в ответе.Установите
htmlвTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном совпадении символа за символом. Пробелы игнорируются в большинстве случаев, порядок атрибутов не существен. СмотритеassertHTMLEqual()для получения дополнительной информации.
-
SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False) -
Проверяет, что
responseвернул заданныйstatus_codeи чтоtextне содержится в егоcontent.Установите
htmlвTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на HTML-семантике, а не на точном соответствии символов. Пробелы в большинстве случаев игнорируются, порядок атрибутов не важен. Подробнее см.assertHTMLEqual().
-
SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None) -
Проверяет, что шаблон с заданным именем был использован при рендеринге ответа.
responseдолжен быть экземпляром ответа, возвращеннымtest client.template_nameдолжен быть строкой, например,'admin/index.html'.Аргумент
count— целое число, указывающее, сколько раз шаблон должен быть рендерирован. По умолчанию этоNone, что означает, что шаблон должен быть рендерирован один или несколько раз.Вы можете использовать его как контекстный менеджер, например, так:
with self.assertTemplateUsed('index.html'): render_to_string('index.html') with self.assertTemplateUsed(template_name='index.html'): render_to_string('index.html')
-
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='') -
Проверяет, что шаблон с заданным именем не использовался при рендеринге ответа.
Вы можете использовать его как контекстный менеджер аналогично
assertTemplateUsed().
-
SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='') -
Проверяет, что два URL-адреса идентичны, игнорируя порядок параметров строки запроса, за исключением параметров с одинаковым именем. Например,
/path/?x=1&y=2равно/path/?y=2&x=1, но/path/?a=1&a=2не равно/path/?a=2&a=1.
-
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) -
Проверяет, что
responseвернул статус перенаправленияstatus_code, перенаправил наexpected_url(включая любыеGETданные), и что конечная страница была получена сtarget_status_code.Если ваш запрос использовал аргумент
follow, тоexpected_urlиtarget_status_codeбудут URL-адресом и кодом состояния конечной точки цепочки перенаправлений.Если
fetch_redirect_responseравноFalse, конечная страница не будет загружена. Так как тестовый клиент не может получать доступ к внешним URL-адресам, это особенно полезно, еслиexpected_urlне входит в ваш приложение Django.Схема обрабатывается корректно при сравнении двух URL-адресов. Если в месте перенаправления не указана схема, используется схема исходного запроса. Если присутствует, используется схема в
expected_urlдля сравнения.
-
SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) -
Проверяет, что строки
html1иhtml2равны. Сравнение основано на HTML-семантике. Сравнение учитывает следующие моменты:- Пробелы перед и после HTML-тегов игнорируются.
- Все типы пробелов рассматриваются как эквивалентные.
- Все открытые теги неявно закрываются, например, когда закрывается окружающий тег или заканчивается HTML-документ.
- Пустые теги эквивалентны их самозакрывающимся версиям.
- Порядок атрибутов HTML-элемента не имеет значения.
- Булевы атрибуты (например,
checked) без аргумента равны атрибутам с одинаковым именем и значением (см. примеры). - Текст, символьные ссылки и ссылки на сущности, которые ссылаются на тот же символ, эквивалентны.
Следующие примеры являются допустимыми тестами и не вызывают никаких
AssertionError:self.assertHTMLEqual( '<p>Hello <b>'world'!</p>', '''<p> Hello <b>'world'! </b> </p>''' ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>' )html1иhtml2должны содержать HTML. Будет вызваноAssertionError, если один из них не может быть проанализирован.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.Изменено в Django 4.0:В более ранних версиях любой атрибут (не только булевые атрибуты) без значения считался равным атрибуту с тем же именем и значением.
-
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.
-
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.base.features.BaseDatabaseFeatures class для получения полного списка функций базы данных, которые могут быть использованы в качестве основы для пропуска тестов.
-
skipIfDBFeature(*feature_name_strings)
Пропускает декорированный тест или TestCase если все перечисленные функции базы данных поддерживаются.
Например, следующий тест не будет выполнен, если база данных поддерживает транзакции (например, он не будет выполняться в PostgreSQL, но будет выполняться в MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
-
skipUnlessDBFeature(*feature_name_strings)
Пропускает декорированный тест или TestCase если хотя бы одна из перечисленных функций базы данных не поддерживается.
Например, следующий тест будет выполнен только если база данных поддерживает транзакции (например, он будет выполняться в PostgreSQL, но не в MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/topics/testing/tools/