Инструменты тестирования
Django предоставляет небольшой набор инструментов, которые могут пригодиться при написании тестов.
Клиент для тестов
Клиент для тестов — это класс Python, который действует как виртуальный веб-браузер, позволяющий тестировать ваши представления и взаимодействовать с вашим приложением Django программно.
Вот некоторые вещи, которые можно делать с клиентом для тестов:
- Имитировать запросы GET и POST к URL и наблюдать за ответом — от низкоуровневого HTTP (заголовки результатов и коды состояния) до содержимого страницы.
- Просматривать цепочку редиректов (если таковые имеются) и проверять URL и код состояния на каждом шаге.
- Проверить, что данный запрос отображается заданным шаблоном Django с контекстом шаблона, содержащим определенные значения.
Обратите внимание, что клиент для тестов не предназначен для замены Selenium или других фреймворков «в браузере». Клиент для тестов Django имеет другое назначение. Короче говоря:
- Используйте клиент для тестов Django, чтобы убедиться, что рендерится правильный шаблон и что шаблон получает правильные данные контекста.
- Используйте фреймворки в браузере, такие как Selenium, для тестирования отображенного HTML и поведения веб-страниц, в частности, функциональности JavaScript. Django также предоставляет специальную поддержку для этих фреймворков; см. раздел о
LiveServerTestCaseдля получения более подробной информации.
Комплектный набор тестов должен использовать комбинацию обоих типов тестов.
Обзор и быстрый пример
Для использования клиента для тестов инициализируйте django.test.Client и получайте веб-страницы:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'
Как показывает этот пример, вы можете инициализировать Client из сессии интерпретатора Python.
Обратите внимание на некоторые важные моменты работы клиента для тестов:
- Клиент для тестов не требует, чтобы веб-сервер был запущен. На самом деле, он будет работать нормально без запущенного веб-сервера! Это потому, что он избегает накладных расходов HTTP и работает непосредственно с фреймворком Django. Это помогает ускорить выполнение модульных тестов.
-
При получении страниц помните, что нужно указать путь URL, а не весь домен. Например, это правильно:
>>> c.get('/login/')Это неправильно:
>>> c.get('https://www.example.com/login/')Клиент для тестов не может получать веб-страницы, которые не работают с вашим проектом Django. Если вам нужно получить другие веб-страницы, используйте модуль стандартной библиотеки Python, такой как
urllib. - Для разрешения URL-адресов клиент для тестов использует конфигурацию URL, на которую указывает ваше значение
ROOT_URLCONF. -
Хотя приведенный выше пример будет работать в интерактивном интерпретаторе Python, некоторые функции клиента для тестов, в частности, связанные с шаблонами, доступны только во время выполнения тестов.
Причина в том, что запуститель тестов Django выполняет некоторую «волшебную» операцию, чтобы определить, какой шаблон был загружен заданным представлением. Эта «волшебная» операция (по сути, подмена системы шаблонов Django в памяти) происходит только во время выполнения тестов.
-
По умолчанию клиент для тестов отключит все проверки CSRF, выполняемые вашим сайтом.
Если по какой-либо причине вам нужно, чтобы клиент для тестов выполнял проверки CSRF, вы можете создать экземпляр клиента для тестов, который будет выполнять проверки CSRF. Для этого передайте аргумент
enforce_csrf_checksпри создании своего клиента:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Создание запросов
Для создания запросов используйте класс django.test.Client.
-
class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults) -
Он не требует аргументов при создании. Однако вы можете использовать ключевые аргументы для указания некоторых стандартных заголовков. Например, это отправит заголовок
User-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')}Отправка файлов — это особый случай. Для отправки файла вам нужно только указать имя поля файла в качестве ключа и указатель на файл, который вы хотите загрузить, в качестве значения. Например:
>>> 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'})… представление, обрабатывающее этот запрос, может получить значения username и password из 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() запросами.
Политики истечения срока действия этих cookie не соблюдаются. Если вам нужно, чтобы cookie истекла, либо удалите её вручную, либо создайте новый экземпляр Client (что фактически удалит все cookie).
У тестового клиента есть два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. Для получения дополнительной информации см. документацию модуляhttp.cookies.
-
Client.session -
Объект, подобный словарю, содержащий информацию о сессии. Для получения подробных сведений см. документацию по сессиям.
Чтобы изменить сессию и сохранить её, необходимо сначала сохранить её в переменной (поскольку каждый раз при обращении к этому свойству создаётся новый
SessionStore):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка
При тестировании приложений, поддерживающих международный и локальный язык, вы можете установить язык для запроса тестового клиента. Способ установки зависит от того, включен ли LocaleMiddleware.
Если middleware включён, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив заголовок Accept-Language HTTP в запросе:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Как Django определяет предпочтения языка.
Если middleware не включён, активный язык можно установить с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Явное задание активного языка.
Пример
Ниже представлен пример unit-теста с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См. также
Предоставленные классы тестовых случаев
Обычные классы тестов Python наследуют от базового класса unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Вы можете преобразовать обычный 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()будет вызываться перед каждым тестом, что сведёт на нет преимущества скорости.Изменено в 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')Изменено в Django 4.0:В более ранних версиях новые колбэки, добавленные во время выполнения
transaction.on_commit()колбэков, не захватывались.
LiveServerTestCase
-
class LiveServerTestCase
LiveServerTestCase выполняет примерно то же самое, что и TransactionTestCase, но с одной дополнительной функцией: он запускает живой сервер Django на заднем плане при настройке и выключает его при завершении. Это позволяет использовать автоматизированные тестовые клиенты, отличные от Django dummy client, такие как, например, клиент Selenium, для выполнения функциональных тестов в браузере и имитации действий реального пользователя.
Живой сервер прослушивает localhost и привязывается к порту 0, который использует свободный порт, назначенный операционной системой. К URL-адресу сервера можно получить доступ с помощью self.live_server_url во время тестов.
Чтобы продемонстрировать, как использовать LiveServerTestCase, давайте напишем тест Selenium. Прежде всего, вам необходимо установить пакет selenium в ваш путь Python:
$ python -m pip install selenium
...\> py -m pip install selenium
Затем добавьте тест, основанный на LiveServerTestCase, в модуль тестов вашего приложения (например: myapp/tests.py). В этом примере мы предположим, что вы используете приложение staticfiles и хотите, чтобы статические файлы предоставлялись во время выполнения тестов так же, как и во время разработки с DEBUG=True, т.е. без необходимости их сбора с помощью collectstatic. Мы будем использовать подкласс StaticLiveServerTestCase, который предоставляет эту функциональность. Замените её на django.test.LiveServerTestCase если вам это не нужно.
Код этого теста может выглядеть следующим образом:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Наконец, вы можете запустить тест следующим образом:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
Этот пример автоматически откроет Firefox, затем перейдет на страницу входа, введёт учетные данные и нажмёт кнопку «Войти». Selenium предлагает другие драйверы на случай, если у вас не установлен Firefox или вы хотите использовать другой браузер. Приведённый выше пример — лишь малая часть того, что может делать Selenium; обратитесь к полной справке для получения более подробной информации.
Примечание
При использовании встроенной базы данных SQLite для запуска тестов, одно и то же подключение к базе данных будет использоваться двумя потоками параллельно: потоком, в котором запускается живой сервер, и потоком, в котором выполняется тестовый случай. Важно предотвратить одновременные запросы к базе данных через это общее подключение двумя потоками, так как это иногда случайным образом приводит к тому, что тесты завершаются сбоем. Поэтому необходимо гарантировать, что оба потока не обращаются к базе данных одновременно. В частности, это означает, что в некоторых случаях (например, сразу после перехода по ссылке или отправки формы) вам может потребоваться проверить, что Selenium получил ответ и что следующая страница загружена перед продолжением выполнения дальнейших тестов. Например, сделайте это, заставив Selenium ждать, пока в ответе не будет найден тег HTML <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, тестовый запуск очистит содержимое тестового ящика вывода электронной почты в начале каждого тестового случая.
Для получения более подробной информации об услугах электронной почты во время тестов см. Услуги электронной почты ниже.
Утверждения
Поскольку обычный класс unittest.TestCase Python реализует методы проверки, такие как assertTrue() и assertEqual(), пользовательский класс TestCase Django предоставляет ряд пользовательских методов проверки, которые полезны для тестирования веб-приложений:
Сообщения об ошибках, возвращаемые большинством этих методов проверки, можно настроить с помощью аргумента 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='') -
Утверждает, что поле на форме вызывает предоставленный список ошибок, когда оно отображается на форме.
responseдолжен быть экземпляром ответа, возвращаемымtest client.form— имя экземпляраForm, заданное в контексте шаблона ответа.field— имя поля на форме, которое нужно проверить. Еслиfieldимеет значениеNone, будут проверены ошибки, не связанные с полем (ошибки, к которым можно получить доступ черезform.non_field_errors()).errors— строка ошибки или список строк ошибок, которые ожидаются в результате проверки формы.
-
SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='') -
Утверждает, что
formsetвызывает предоставленный список ошибок при рендеринге.responseдолжен быть экземпляром ответа, возвращённым методомtest client.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содержится в его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.Изменено в 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()). Например, вот как установить HTTP-заголовокAccept:>>> 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.0/topics/testing/tools/