Инструменты тестирования
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)[source]
-
Он не требует аргументов во время создания. Однако, вы можете использовать ключевые аргументы для указания некоторых значений по умолчанию для заголовков. Например, это отправит HTTP-заголовок
User-Agentв каждом запросе:>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
Значения из ключевых аргументов
extra, переданных вget(),post()и т. д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.Аргумент
enforce_csrf_checksможет быть использован для тестирования защиты от CSRF (см. выше).Аргумент
json_encoderпозволяет задать пользовательский кодировщик JSON для сериализации JSON, описанной вpost().Изменено в Django 2.1:Добавлен аргумент
json_encoder.После получения экземпляра
Client, вы можете вызвать любой из следующих методов:-
get(path, data=None, follow=False, secure=False, **extra)[source] -
Выполняет запрос GET по предоставленному
pathи возвращает объектResponse, документация которого приведена ниже.Ключево-значимые пары в словаре
dataиспользуются для создания данных запроса GET. Например:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})…приведёт к выполнению запроса GET, эквивалентного:
/customers/details/?name=fred&age=7
Ключевые аргументы
extraмогут использоваться для указания заголовков, которые будут отправлены в запросе. Например:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_X_REQUESTED_WITH='XMLHttpRequest')…отправит HTTP-заголовок
HTTP_X_REQUESTED_WITHв представление деталей, что является хорошим способом тестирования кода, использующего методdjango.http.HttpRequest.is_ajax().Спецификация CGI
Заголовки, отправленные через
**extra, должны соответствовать спецификации CGI. Например, эмуляция другого заголовка «Host», отправленного в HTTP-запросе от браузера к серверу, должна быть передана какHTTP_HOST.Если у вас уже есть данные GET в формате URL-кодирования, вы можете использовать это кодирование вместо аргумента data. Например, предыдущий запрос GET также можно представить как:
>>> c = Client() >>> c.get('/customers/details/?name=fred&age=7')Если вы предоставите URL с кодированными данными GET и аргументом data, аргумент data будет иметь приоритет.
Если вы установите
followв значениеTrue, клиент будет следовать всем редиректам, а в объекте ответа будет установлен атрибутredirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.Если у вас был URL
/redirect_me/, который перенаправлял на/next/, а тот — на/final/, вот что вы увидите:>>> response = c.get('/redirect_me/', follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]Если вы установите
secureв значениеTrue, клиент будет эмулировать запрос HTTPS.
-
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)[source] -
Выполняет запрос POST по предоставленному
pathи возвращает объектResponse, документация которого приведена ниже.Ключево-значимые пары в словаре
dataиспользуются для отправки данных POST. Например:>>> c = Client() >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})…приведёт к выполнению запроса POST по данному URL:
/login/
…с этими данными POST:
name=fred&passwd=secret
Если вы укажете
content_typeкак application/json, словарьdataсериализуется с помощьюjson.dumps()иDjangoJSONEncoder. Вы можете изменить кодировщик, предоставив аргументjson_encoderметодуClient. Эта сериализация также происходит для запросовput(),patch()иdelete().Изменено в Django 2.1:Была добавлена описанная выше сериализация JSON. В более старых версиях вы можете вызвать
json.dumps()наdataперед передачей его вpost(), чтобы добиться того же результата.Если вы укажете любой другой
content_type(например, text/xml для XML-загрузки), содержимоеdataбудет отправлено как есть в запросе POST, используяcontent_typeв HTTP-заголовкеContent-Type.Если вы не укажете значение для
content_type, значения вdataбудут переданы со значением типа содержимого multipart/form-data. В этом случае ключево-значимые пары вdataбудут закодированы как multipart-сообщение и использованы для создания данных загрузки POST.Чтобы отправить несколько значений для одного ключа — например, для указания выделенных значений для
<select multiple>, — укажите значения в виде списка или кортежа для необходимого ключа. Например, это значениеdataотправит три выбранных значения для поля с именемchoices.{'choices': ('a', 'b', 'd')}Загрузка файлов — это особый случай. Чтобы загрузить файл, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:
>>> c = Client() >>> with open('wishlist.doc') as fp: ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})(Имя
attachmentздесь не имеет значения; используйте любое имя, ожидаемое вашим кодом обработки файлов.)Вы также можете передать любой подобный файлу объект (например,
StringIOилиBytesIO) как дескриптор файла. Если вы загружаете вImageField, объекту требуется атрибутname, который проходит валидаторvalidate_image_file_extension. Например:>>> from io import BytesIO >>> img = BytesIO(b'mybinarydata') >>> img.name = 'myimage.jpg'
Обратите внимание, что если вы хотите использовать тот же дескриптор файла для нескольких вызовов
post(), вам нужно будет вручную сбросить указатель файла между отправками. Самый простой способ сделать это — вручную закрыть файл после его предоставления методуpost(), как показано выше.Также убедитесь, что файл открыт таким образом, чтобы данные могли быть прочитаны. Если ваш файл содержит двоичные данные, такие как изображение, это означает, что вам нужно открыть файл в режиме
rb(чтение двоичных данных).Аргумент
extraработает так же, как и дляClient.get().Если URL, который вы запрашиваете с помощью POST, содержит кодированные параметры, эти параметры будут доступны в данных запроса request.GET. Например, если вы выполните запрос:
>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})…представление, обрабатывающее этот запрос, может использовать request.POST для извлечения имени пользователя и пароля и request.GET, чтобы определить, был ли пользователь гостем.
Если вы установите
followв значениеTrue, клиент будет следовать всем редиректам, а в объекте ответа будет установлен атрибутredirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.Если вы установите
secureв значениеTrue, клиент будет эмулировать запрос HTTPS.
-
head(path, data=None, follow=False, secure=False, **extra)[source] -
Выполняет запрос HEAD по предоставленному
pathи возвращает объектResponse. Этот метод работает точно так же, какClient.get(), включая аргументыfollow,secureиextra, за исключением того, что он не возвращает тело сообщения.
-
options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source] -
Выполняет запрос 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)[source] -
Выполняет запрос 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)[source] -
Выполняет запрос PATCH по предоставленному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Аргументы
follow,secureиextraработают так же, как и дляClient.get().
-
-
delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source] -
Выполняет запрос DELETE к указанному
pathи возвращает объектResponse. Полезно для тестирования RESTful-интерфейсов.Если указан
data, он используется в качестве тела запроса, и заголовокContent-Typeустанавливается в значениеcontent_type.Аргументы
follow,secureиextraдействуют так же, как и дляClient.get().
-
trace(path, follow=False, secure=False, **extra)[source] -
Выполняет запрос TRACE к указанному
pathи возвращает объектResponse. Полезно для имитации диагностических запросов.В отличие от других методов запроса,
dataне передаётся как ключевой параметр, чтобы соответствовать RFC 7231#section-4.3.8, который предписывает, что запросы TRACE не должны иметь тело.Аргументы
follow,secure, иextraдействуют так же, как и дляClient.get().
-
login(**credentials)[source] -
Если ваш сайт использует систему аутентификации 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)[source] -
Если ваш сайт использует систему аутентификации Django, вы можете использовать метод
force_login()для моделирования входа пользователя на сайт. Используйте этот метод вместоlogin(), когда тест требует, чтобы пользователь был авторизован, а подробности входа не важны.В отличие от
login(), этот метод пропускает шаги аутентификации и проверки: неактивные пользователи (is_active=False) могут войти, и учетные данные пользователя предоставлять не нужно.У пользователя будет установлен атрибут
backendсо значением аргументаbackend(который должен быть строкой с путём к объекту Python), или со значениемsettings.AUTHENTICATION_BACKENDS[0], если значение не указано. Функцияauthenticate(), вызываемаяlogin(), обычно аннотирует пользователя таким образом.Этот метод быстрее, чем
login(), поскольку дорогостоящие алгоритмы хэширования паролей пропущены. Также, вы можете ускоритьlogin()используя более слабый хэшер при тестировании.
-
logout()[source] -
Если ваш сайт использует систему аутентификации 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может быть подходящей альтернативой для ответов с этим атрибутом.
-
json(**kwargs) -
Тело ответа, разобранное как JSON. Дополнительные ключевые аргументы передаются в
json.loads(). Например:>>> response = client.get('/foo/') >>> response.json()['name'] 'Arthur'Если заголовок
Content-Typeне"application/json", то при попытке разбора ответа будет поднята ошибкаValueError.
-
request -
Данные запроса, которые вызвали ответ.
-
wsgi_request -
Экземпляр
WSGIRequest, сгенерированный обработчиком теста, который сгенерировал ответ.
-
status_code -
HTTP статус ответа, как целое число. Полный список определённых кодов см. в реестре кодов статусов IANA.
-
templates -
Список экземпляров
Template, используемых для отрисовки окончательного содержимого, в порядке их отрисовки. Для каждого шаблона в списке, используйтеtemplate.nameдля получения имени файла шаблона, если шаблон был загружен из файла. (Имя — это строка, такая как'admin/index.html'.)Не используете Django шаблоны?
Этот атрибут заполняется только при использовании бэкэнда
DjangoTemplates. Если вы используете другой движок шаблонов,template_nameможет быть подходящей альтернативой, если вам нужно только имя шаблона, используемого для отрисовки.
-
resolver_match -
Экземпляр
ResolverMatchдля ответа. Вы можете использовать атрибутfunc, например, для проверки представления, которое обслужило ответ:# my_view here is a function based view self.assertEqual(response.resolver_match.func, my_view) # class-based views need to be compared by name, as the functions # generated by as_view() won't be equal self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__)
Если указанный URL не найден, обращение к этому атрибуту вызовет исключение
Resolver404.
-
Вы также можете использовать синтаксис словаря для объекта ответа, чтобы запросить значение любых настроек в HTTP-заголовках. Например, вы можете определить тип содержимого ответа с помощью response['Content-Type'].
Исключения
Если вы направите тестовый клиент на представление, которое поднимает исключение, это исключение будет видно в тестовом случае. Затем вы можете использовать стандартный блок try ... except или assertRaises() для проверки исключений.
Единственными исключениями, которые не видны тестовому клиенту, являются Http404, PermissionDenied, SystemExit и SuspiciousOperation. Django обрабатывает эти исключения внутри и преобразует их в соответствующие коды HTTP-ответа. В этих случаях вы можете проверить response.status_code в своём тесте.
Состояние
Тестовый клиент имеет состояние. Если ответ возвращает cookie, то эта cookie будет сохранена в тестовом клиенте и отправлена со всеми последующими get() и post() запросами.
Политики истечения срока действия этих cookie не соблюдаются. Если вы хотите, чтобы cookie истекла, удалите её вручную или создайте новый экземпляр Client (что фактически удалит все cookie).
У тестового клиента есть два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. Для получения более подробной информации см. документацию модуляhttp.cookies.
-
Client.session -
Объект, похожий на словарь, содержащий информацию о сессии. Для получения подробных сведений см. документацию по сессиям.
Чтобы изменить сессию и затем сохранить её, её необходимо сначала сохранить в переменной (потому что каждый раз при доступе к этому свойству создаётся новая
SessionStore):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка
При тестировании приложений, поддерживающих международные и локальные настройки, вы можете установить язык для запроса тестового клиента. Способ зависит от того, включён ли LocaleMiddleware.
Если middleware включён, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив заголовок Accept-Language HTTP в запросе:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробная информация в Как Django определяет предпочтительный язык.
Если middleware не включён, активный язык можно установить с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробная информация в Явное задание активного языка.
Пример
Следующий пример — простой модульный тест с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См. также
Предоставленные классы тестовых случаев
Обычные классы тестов Python наследуются от базового класса unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Иерархия классов Django для юнит-тестирования
Преобразование обычного unittest.TestCase в любой из подклассов простое: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные функции Python для юнит-тестирования будут доступны, и они будут дополнены полезными добавками, описанными в каждом разделе ниже.
SimpleTestCase
-
class SimpleTestCase[source]
Подкласс 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. - Проверка выполнения HTTP
redirectприложением. - Надежное тестирование двух
HTML fragmentsна равенство/неравенство илиcontainment. - Надежное тестирование двух
XML fragmentsна равенство/неравенство. - Надежное тестирование двух
JSON fragmentsна равенство.
- Проверка того, что вызываемый объект
- Возможность запуска тестов с изменёнными настройками.
- Использование
clientClient.
Если ваши тесты выполняют запросы к базе данных, используйте подклассы TransactionTestCase или TestCase.
-
SimpleTestCase.allow_database_queries -
SimpleTestCaseпо умолчанию запрещает запросы к базе данных. Это помогает избежать выполнения записывающих запросов, которые повлияют на другие тесты, так как каждыйSimpleTestCaseтест не выполняется в транзакции. Если вы не беспокоитесь об этой проблеме, вы можете отключить это поведение, установив атрибут классаallow_database_queriesвTrueв вашем классе тестов.
Предупреждение
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[source]
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[source]
Это наиболее распространённый класс для написания тестов в Django. Он наследуется от TransactionTestCase (и, следовательно, от SimpleTestCase). Если ваше приложение Django не использует базу данных, используйте SimpleTestCase.
Класс:
- Оборачивает тесты двумя вложенными блоками
atomic(): одним для всего класса и одним для каждого теста. Поэтому, если вы хотите проверить поведение конкретной транзакции базы данных, используйтеTransactionTestCase. - Проверяет откладываемые ограничения базы данных в конце каждого теста.
Он также предоставляет дополнительный метод:
-
classmethod TestCase.setUpTestData()[source] -
Указанный выше блок уровня класса
atomicпозволяет создавать начальные данные на уровне класса, один раз для всегоTestCase. Этот метод позволяет ускорить тесты по сравнению с использованиемsetUp().Например:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...Обратите внимание, что если тесты выполняются на базе данных без поддержки транзакций (например, MySQL с движком MyISAM),
setUpTestData()будет вызываться перед каждым тестом, исключая преимущества скорости.Будьте внимательны, не изменяйте объекты, созданные в
setUpTestData()в ваших методах тестирования. Изменения в объектах из работы на уровне класса, выполненной при настройке, сохраняются между методами тестирования. Если вам нужно их изменить, вы можете перезагрузить их в методеsetUp()с помощьюrefresh_from_db(), например.
LiveServerTestCase
-
class LiveServerTestCase[source]
LiveServerTestCase выполняет примерно то же самое, что и TransactionTestCase с одной дополнительной функцией: на этапе подготовки запускает активный сервер Django в фоновом режиме и останавливает его на этапе завершения. Это позволяет использовать автоматизированные клиентские программы тестирования, отличные от стандартного тестового клиента Django, такие как, например, клиент Selenium, для выполнения серии функциональных тестов в браузере и имитации действий реального пользователя.
Активный сервер прослушивает localhost и привязывается к порту 0, который использует свободный порт, назначенный операционной системой. К URL-адресу сервера можно получить доступ с помощью self.live_server_url во время тестирования.
Чтобы продемонстрировать, как использовать LiveServerTestCase, давайте напишем простой тест Selenium. Прежде всего, вам необходимо установить пакет selenium в ваш путь Python:
$ pip install selenium
...\> pip install selenium
Затем добавьте тест, основанный на LiveServerTestCase, в модуль тестов вашего приложения (например: myapp/tests.py). В этом примере мы будем предполагать, что вы используете приложение staticfiles и хотите, чтобы статические файлы обслуживались во время выполнения ваших тестов аналогично тому, что мы получаем на этапе разработки с DEBUG=True, т. е. без необходимости собирать их с помощью collectstatic. Мы будем использовать подкласс StaticLiveServerTestCase, который предоставляет эту функциональность. Замените его на django.test.LiveServerTestCase, если вам это не нужно.
Код этого теста может выглядеть следующим образом:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Наконец, вы можете запустить тест следующим образом:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
В этом примере автоматически откроется Firefox, затем откроется страница входа, будут введены учетные данные и нажата кнопка «Войти». Selenium предлагает других драйверы на случай, если у вас не установлен Firefox или вы хотите использовать другой браузер. Приведенный выше пример — лишь малая часть того, что может делать клиент Selenium; обратитесь к полной документации для получения более подробной информации.
Примечание
При использовании встроенной базы данных SQLite для выполнения тестов одно и то же подключение к базе данных будет совместно использоваться двумя потоками параллельно: потоком, в котором запущен активный сервер, и потоком, в котором запущен тестовый случай. Важно предотвратить одновременные запросы к базе данных через это общее подключение двумя потоками, так как это иногда случайно приводит к сбоям тестов. Таким образом, вы должны убедиться, что два потока не обращаются к базе данных одновременно. В частности, это означает, что в некоторых случаях (например, сразу после нажатия ссылки или отправки формы) вам может потребоваться убедиться, что Selenium получил ответ и что следующая страница загрузилась перед продолжением дальнейшего выполнения теста. Сделайте это, например, заставив Selenium ждать, пока тег <body> не будет найден в ответе (требуется Selenium > 2.13):
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element_by_tag_name('body'))
Трудность заключается в том, что в современных веб-приложениях, которые генерируют HTML динамически после того, как сервер сгенерировал начальный документ, фактически нет такого понятия, как «загрузка страницы». Поэтому простое проверка наличия <body> в ответе может быть не всегда уместна во всех случаях. Обратитесь к часто задаваемым вопросам Selenium и документации Selenium для получения дополнительной информации.
Функциональные возможности тестовых случаев
Стандартный тестовый клиент
-
SimpleTestCase.client
Каждый тестовый случай в экземпляре django.test.*TestCase имеет доступ к экземпляру тестового клиента Django. К этому клиенту можно обратиться как self.client. Этот клиент пересоздается для каждого теста, поэтому вам не нужно беспокоиться о том, что состояние (например, cookie) будет передаваться от одного теста к другому.
Это означает, что вместо создания экземпляра Client в каждом тесте:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
… вы можете просто обратиться к self.client, как показано ниже:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
Настройка тестового клиента
-
SimpleTestCase.client_class
Если вы хотите использовать другой класс Client (например, подкласс с настраиваемым поведением), используйте атрибут класса client_class:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Загрузка фикстур
-
TransactionTestCase.fixtures
Тестовый случай для веб-сайта с базой данных не очень полезен, если в базе данных нет данных. Для создания объектов с помощью ORM, например, в TestCase.setUpTestData(), тесты более читабельны и их проще поддерживать. Однако вы также можете использовать фикстуры.
Фикстура — это набор данных, который Django знает, как импортировать в базу данных. Например, если ваш сайт имеет учетные записи пользователей, вы можете создать фикстуру с фиктивными учетными записями пользователей для заполнения вашей базы данных во время тестирования.
Самый простой способ создания фикстуры — использовать команду manage.py dumpdata. Это предполагает, что у вас уже есть какие-то данные в вашей базе данных. См. dumpdata
documentation для получения дополнительной информации.
После создания фикстуры и размещения её в каталоге fixtures в одном из ваших INSTALLED_APPS, вы можете использовать её в своих модульных тестах, указав атрибут класса fixtures в вашем подклассе django.test.TestCase:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Вот что произойдёт:
- В начале каждого теста, перед выполнением
setUp(), Django очистит базу данных, вернув её в состояние, в котором она была сразу после вызоваmigrate. - Затем устанавливаются все указанные фикстуры. В этом примере Django установит все JSON-фикстуры, имеющие имя
mammals, за которыми последуют все фикстуры с именемbirds. См. документациюloaddataдля получения более подробной информации о определении и установке фикстур.
Из соображений производительности, TestCase загружает фикстуры один раз для всего класса тестов, перед setUpTestData(), а не перед каждым тестом, и использует транзакции для очистки базы данных перед каждым тестом. В любом случае, вы можете быть уверены, что результат теста не повлияет на другой тест или порядок выполнения тестов.
По умолчанию фикстуры загружаются только в базу данных default. Если вы используете несколько баз данных и установили multi_db=True, фикстуры будут загружены во все базы данных.
Настройка URLconf
Если ваше приложение предоставляет представления, вам, возможно, захочется включить тесты, которые используют тестовый клиент для проверки этих представлений. Однако конечный пользователь может развернуть представления в вашем приложении по любому URL-адресу по своему выбору. Это означает, что ваши тесты не могут полагаться на то, что ваши представления будут доступны по определенному URL-адресу. Используйте декоратор @override_settings(ROOT_URLCONF=...) для настройки URLconf.
Поддержка нескольких баз данных
-
TransactionTestCase.multi_db
Django создаёт тестовую базу данных, соответствующую каждой базе данных, определенной в DATABASES в файле настроек. Однако большая часть времени, затрачиваемого на выполнение Django TestCase, расходуется на вызов flush, который гарантирует, что у вас есть чистая база данных в начале каждого теста. Если у вас несколько баз данных, требуется несколько очисток (по одной для каждой базы данных), что может быть длительным процессом, особенно если ваши тесты не требуют проверки деятельности с несколькими базами данных.
В целях оптимизации Django очищает только базу данных default в начале каждого теста. Если ваша настройка содержит несколько баз данных и у вас есть тест, который требует очистки каждой базы данных, вы можете использовать атрибут multi_db в наборе тестов для запроса полной очистки.
Например:
class TestMyViews(TestCase):
multi_db = True
def test_index_page_view(self):
call_some_test_code()
Этот тестовый случай очистит все тестовые базы данных перед запуском test_index_page_view.
Флаг multi_db также влияет на базы данных, в которые загружаются TransactionTestCase.fixtures. По умолчанию (когда multi_db=False) фикстуры загружаются только в базу данных default. Если multi_db=True, фикстуры загружаются во все базы данных.
Переопределение настроек
Предупреждение
Используйте приведенные ниже функции для временного изменения значения настроек в тестах. Не манипулируйте django.conf.settings напрямую, так как Django не восстановит исходные значения после таких манипуляций.
-
SimpleTestCase.settings()[source]
Для целей тестирования часто бывает полезно временно изменить настройку и вернуть её к исходному значению после выполнения кода тестирования. Для этого случая 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()[source]
Переопределение настроек, содержащих список значений, может оказаться неудобным. На практике часто достаточно добавления или удаления значений. Менеджер контекста 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()[source]
В случае, если вы хотите переопределить настройку для метода теста, 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()[source]
Аналогично, 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().
Учитывая Python 3.5
При использовании Python 3.5 (или более старых версий, если используется более старая версия Django), избегайте смешивания remove с append и prepend в modify_settings(). В некоторых случаях имеет значение, добавляется ли значение сначала, а затем удаляется, или наоборот, а порядок ключей словаря не сохраняется до Python 3.6. Вместо этого применяйте декоратор дважды, чтобы гарантировать порядок операций. Например, чтобы убедиться, что SessionMiddleware появляется первым в MIDDLEWARE,:
@modify_settings(MIDDLEWARE={
'remove': ['django.contrib.sessions.middleware.SessionMiddleware'],
)
@modify_settings(MIDDLEWARE={
'prepend': ['django.contrib.sessions.middleware.SessionMiddleware'],
})
Предупреждение
Файл настроек содержит некоторые настройки, которые используются только во время инициализации внутренних компонентов 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 | Стандартное хранилище файлов |
Очистка тестовой папки выходящих сообщений
Если вы используете любые из пользовательских классов Django TestCase, запуск тестов очистит содержимое тестовой папки выходящих сообщений электронной почты в начале каждого тестового случая.
Дополнительные сведения об услугах электронной почты во время тестов см. ниже в разделе Услуги электронной почты.
Ассерции
Поскольку стандартный класс Python unittest.TestCase реализует методы утверждений, такие как assertTrue() и assertEqual(), пользовательский класс Django TestCase предоставляет ряд пользовательских методов утверждений, полезных для тестирования веб-приложений:
Сообщения об ошибках, выдаваемые большинством этих методов утверждений, могут быть настроены с помощью аргумента msg_prefix. Эта строка будет добавлена к любому сообщению об ошибке, сгенерированному утверждением. Это позволяет предоставить дополнительные сведения, которые могут помочь определить расположение и причину ошибки в наборе тестов.
-
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[source] -
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)[source] -
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message) -
Добавлена в Django 2.1.
Аналогично
SimpleTestCase.assertRaisesMessage(), но дляassertWarnsRegex()вместоassertRaisesRegex().
-
SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[source] -
Проверяет, что поле формы ведет себя корректно с различными входными данными.
Параметры: - 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='')[source] -
Проверяет, что поле на форме вызывает указанный список ошибок при отображении на форме.
form— имя, которое было дано экземпляруFormв контексте шаблона.field— имя поля на форме для проверки. Еслиfieldимеет значениеNone, будут проверены ошибки, не относящиеся к полю (ошибки, которые можно получить с помощьюform.non_field_errors()).errors— строка ошибки или список строк ошибок, ожидаемых в результате проверки формы.
-
SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='')[source] -
Проверяет, что
formsetвызывает предоставленный список ошибок при отображении.formset— имя, которое было дано экземпляруFormsetв контексте шаблона.form_index— номер формы вFormset. Еслиform_indexимеет значениеNone, будут проверены ошибки, не относящиеся к форме (ошибки, которые можно получить с помощьюformset.non_form_errors()).field— имя поля на форме для проверки. Еслиfieldимеет значениеNone, будут проверены ошибки, не относящиеся к полю (ошибки, которые можно получить с помощьюform.non_field_errors()).errors— строка ошибки или список строк ошибок, ожидаемых в результате проверки формы.
-
SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[source] -
Проверяет, что экземпляр
Responseпроизвел указанныйstatus_codeи чтоtextприсутствует в содержимом ответа. Если указанcount,textдолжен появиться ровноcountраз в ответе.Установите
htmlвTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. См.assertHTMLEqual()для получения дополнительной информации.
-
SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[source] -
Проверяет, что экземпляр
Responseпроизвел указанныйstatus_codeи чтоtextне присутствует в содержимом ответа.Установите
htmlвTrueдля обработкиtextкак HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не имеет значения. См.assertHTMLEqual()для получения дополнительной информации.
-
SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[source] -
Проверяет, что шаблон с заданным именем использовался при отрисовке ответа.
Имя — строка, например
'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='')[source] -
Проверяет, что шаблон с заданным именем не использовался при отрисовке ответа.
Можно использовать его как менеджер контекста аналогично
assertTemplateUsed().
-
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[source] -
Проверяет, что ответ вернул статус переадресации
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)[source] -
Проверяет, что строки
html1иhtml2равны. Сравнение основано на семантике HTML. Сравнение учитывает следующие моменты:- Пробелы до и после тегов HTML игнорируются.
- Все типы пробелов считаются эквивалентными.
- Все открытые теги неявно закрываются, например, когда закрывается окружающий тег или заканчивается HTML-документ.
- Пустые теги эквивалентны их самозакрывающимся версиям.
- Порядок атрибутов HTML-элемента не имеет значения.
- Атрибуты без аргумента равны атрибутам, равным по имени и значению (см. примеры).
Следующие примеры являются допустимыми тестами и не вызывают никаких
AssertionError.self.assertHTMLEqual( '<p>Hello <b>world!</p>', '''<p> Hello <b>world! </b> </p>''' ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>' )html1иhtml2должны быть корректным HTML. Будет возбуждено исключениеAssertionError, если один из них не может быть распарсен.Выход в случае ошибки можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[source] -
Проверяет, что строки
html1иhtml2не равны. Сравнение основано на семантике HTML. См.assertHTMLEqual()для получения дополнительной информации.html1иhtml2должны быть корректным HTML. Будет возбуждено исключениеAssertionError, если один из них не может быть распарсен.Выход в случае ошибки можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[source] -
Проверяет, что строки
xml1иxml2равны. Сравнение основано на семантике XML. АналогичноassertHTMLEqual(), сравнение выполняется на обработанном содержимом, следовательно, учитываются только семантические, а не синтаксические различия. Если любой параметр содержит некорректный XML, всегда возбуждается исключениеAssertionError, даже если обе строки идентичны.Выход в случае ошибки можно настроить с помощью аргумента
msg.
-
SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[source] -
Утверждает, что строки
xml1иxml2не равны. Сравнение основано на XML-семантике. Подробности см. вassertXMLEqual().Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[source] -
Утверждает, что HTML-фрагмент
needleсодержится вhaystack.Если указан целочисленный аргумент
count, то дополнительно будет строго проверено количествоneedleвхождений.Пробелы в большинстве случаев игнорируются, а порядок атрибутов не имеет значения. Передаваемые аргументы должны быть валидным HTML.
-
SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[source] -
Утверждает, что JSON-фрагменты
rawиexpected_dataравны. Обычно применяются правила JSON относительно незначительных пробелов, так как основная обработка делегируется библиотекеjson.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[source] -
Утверждает, что JSON-фрагменты
rawиexpected_dataне равны. Дополнительные детали см. вassertJSONEqual().Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True, msg=None)[source] -
Утверждает, что набор запросов
qsвозвращает определённый список значенийvalues.Сравнение содержимого
qsиvaluesвыполняется с помощью функцииtransform; по умолчанию это означает, что сравниваетсяrepr()каждого значения. Любая другая вызываемая функция может быть использована, еслиrepr()не предоставляет уникального или полезного сравнения.По умолчанию сравнение также зависит от порядка. Если
qsне предоставляет неявного порядка, можно установить параметрorderedв значениеFalse, что превратит сравнение вcollections.Counterсравнение. Если порядок неопределён (если заданныйqsне упорядочен и сравнение выполняется с более чем одним упорядоченным значением), возникаетValueError.Вывод в случае ошибки может быть настроен с помощью аргумента
msg.
-
TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[source] -
Утверждает, что при вызове
funcс*argsи**kwargsвыполняетсяnumбаз данных.Если ключ
"using"присутствует вkwargs, он используется в качестве псевдонима базы данных, для которого необходимо проверить количество запросов. Если вы хотите вызвать функцию с параметром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):
...
'slow', 'core', 'bar', и 'foo'.
В более ранних версиях помеченные тесты не наследуют теги от классов, а помеченные подклассы не наследуют теги от суперклассов. Например, SampleTestCaseChild.test помечен только тегом 'bar'.
Затем вы можете выбрать, какие тесты запускать. Например, для запуска только быстрых тестов:
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
Или для запуска быстрых тестов и основных (даже если они медленные):
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
Вы также можете исключить тесты по тегу. Для запуска основных тестов, если они не медленные:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag имеет приоритет перед test --tag, поэтому, если у теста два тега и вы выбираете один из них, а исключаете другой, тест не будет запущен.
Сервисы электронной почты
Если какие-либо ваши Django-представления отправляют электронные письма с использованием функциональности электронной почты Django, вам, вероятно, не нужно отправлять электронные письма каждый раз, когда вы запускаете тест с использованием этой представления. По этой причине тестовый запуск Django автоматически перенаправляет все отправленные Django электронные письма в буфер. Это позволяет проверить все аспекты отправки электронных писем – от количества отправленных сообщений до содержимого каждого сообщения – без фактического отправления сообщений.
Тестовый запуск достигает этого путём прозрачной замены стандартного почтового бэкенда тестовым бэкендом. (Не беспокойтесь – это никак не влияет на другие отправители электронной почты за пределами Django, такие как почтовый сервер вашего компьютера, если вы его используете.)
-
django.core.mail.outbox
Во время тестирования каждое исходящее электронное письмо сохраняется в django.core.mail.outbox. Это простой список всех экземпляров EmailMessage, которые были отправлены. Атрибут outbox — это специальный атрибут, который создаётся только при использовании тестового бэкенда электронной почты. Он обычно не существует как часть модуля django.core.mail и вы не можете импортировать его напрямую. Приведённый ниже код показывает, как правильно получить доступ к этому атрибуту.
Вот пример теста, который проверяет django.core.mail.outbox по длине и содержимому:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
'Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, 'Subject here')
Как упоминалось ранее ранее, тестовый буфер очищается в начале каждого теста в Django *TestCase. Чтобы очистить буфер вручную, присвойте пустой список mail.outbox:
from django.core import mail # Empty the test outbox mail.outbox = []
Команды управления
Команды управления могут быть протестированы с помощью функции call_command(). Вывод можно перенаправить в экземпляр StringIO:
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())
Пропуск тестов
Библиотека unittest предоставляет декораторы @skipIf и @skipUnless, которые позволяют пропускать тесты, если заранее известно, что эти тесты будут проваливаться в определённых условиях.
Например, если ваш тест требует наличия определённой дополнительной библиотеки для успешного выполнения, вы можете использовать декоратор @skipIf. Тогда тестовый запуск сообщит, что тест не был выполнен и по какой причине, вместо того, чтобы завершить тест с ошибкой или пропустить его вовсе.
Для дополнения этих поведений пропуска тестов, Django предоставляет два дополнительных декоратора пропуска. Вместо проверки общего булевого значения, эти декораторы проверяют возможности базы данных и пропускают тест, если база данных не поддерживает определённую именованную функцию.
Декораторы используют строковый идентификатор для описания функций базы данных. Эта строка соответствует атрибутам класса функций подключения к базе данных. См. класс django.db.backends.BaseDatabaseFeatures для полного списка функций базы данных, которые могут быть использованы в качестве основы для пропуска тестов.
-
skipIfDBFeature(*feature_name_strings)[source]
Пропустить декорированный тест или TestCase если все именованные функции базы данных поддерживаются.
Например, следующий тест не будет выполнен, если база данных поддерживает транзакции (например, он не будет запущен под PostgreSQL, но будет под MySQL с таблицами MyISAM):
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
-
skipUnlessDBFeature(*feature_name_strings)[source]
Пропустить декорированный тест или 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/2.1/topics/testing/tools/