Инструменты тестирования
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, **defaults)[source] -
Он не требует аргументов во время создания. Однако вы можете использовать ключевые аргументы для указания некоторых значений по умолчанию для заголовков. Например, это отправит заголовок
User-Agentв каждом запросе:>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
Значения из ключевых аргументов
extra, переданных вget(),post()и т.д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.Аргумент
enforce_csrf_checksможет использоваться для тестирования защиты от CSRF (см. выше).После получения экземпляра
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(например, text/xml для XML-данных), содержимоеdataбудет отправлено как есть в запросе POST, используяcontent_typeв заголовке HTTPContent-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'})…представление, обрабатывающее этот запрос, может получить username и password из 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()тестового клиента для моделирования эффекта входа пользователя на сайт.После вызова этого метода тестовый клиент будет иметь все необходимые файлы cookie и данные сеанса для прохождения любых тестов, основанных на входе, которые могут быть частью представления.
Формат аргумента
credentialsзависит от используемого модуля аутентификации (который настраивается вашим параметромAUTHENTICATION_BACKENDS). Если вы используете стандартный модуль аутентификации Django (ModelBackend),credentialsдолжны быть именем пользователя и паролем, предоставленными в качестве ключевых аргументов:>>> c = Client() >>> c.login(username='fred', password='secret') # Now you can access a view that's only available to logged-in users.
Если вы используете другой модуль аутентификации, для этого метода могут потребоваться другие учетные данные. Он требует учетных данных, необходимых для метода
authenticate()вашего модуля.login()возвращаетTrueв случае успешного входа.Наконец, вам нужно будет создать учетные записи пользователей, прежде чем вы сможете использовать этот метод. Как мы объясняли выше, тестовый запуск выполняется с использованием тестовой базы данных, которая по умолчанию не содержит пользователей. В результате учетные записи пользователей, которые действительны на вашем сайте в режиме реального времени, не будут работать в тестовых условиях. Вам нужно будет создавать пользователей в составе набора тестов — либо вручную (используя API модели Django), либо с помощью тестового фикстура. Помните, что если вы хотите, чтобы ваш тестовый пользователь имел пароль, вы не можете установить пароль пользователя, установив атрибут пароля напрямую — вы должны использовать функцию
set_password()для хранения правильно хешированного пароля. В качестве альтернативы, вы можете использовать вспомогательный методcreate_user()для создания нового пользователя с правильно хешированным паролем.
-
force_login(user, backend=None)[source] -
Если ваш сайт использует систему аутентификации Django, вы можете использовать метод
force_login()для моделирования эффекта входа пользователя на сайт. Используйте этот метод вместоlogin()в тех случаях, когда тест требует входа пользователя, и детали того, как пользователь вошел, не важны.В отличие от
login(), этот метод пропускает шаги аутентификации и проверки: неактивные пользователи (is_active=False) могут войти в систему, и не нужно предоставлять учетные данные пользователя.У пользователя будет установлено атрибут
backendв значение аргументаbackend(который должен быть строкой пути Python с точками) или в значениеsettings.AUTHENTICATION_BACKENDS[0]если значение не указано. Функцияauthenticate(), вызываемая методомlogin(), обычно выполняет аннотацию пользователя таким образом.Этот метод быстрее, чем
login(), так как дорогие алгоритмы хеширования паролей опущены. Кроме того, вы можете ускоритьlogin()с помощью использования более слабого хешировщика при тестировании.
-
logout()[source] -
Если ваш сайт использует систему аутентификации Django, метод
logout()может использоваться для моделирования эффекта выхода пользователя из системы.После вызова этого метода тестовый клиент очистит все файлы cookie и данные сеанса до значений по умолчанию. Последующие запросы будут отображаться как исходящие от
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() запросами.
Политики истечения срока действия этих cookies не соблюдаются. Если вы хотите, чтобы cookie истекла, удалите ее вручную или создайте новый экземпляр Client (что эффективно удалит все cookies).
У тестового клиента есть два атрибута, хранящие информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.
-
Объект Python
SimpleCookie, содержащий текущие значения всех cookies клиента. Смотрите документацию модуляhttp.cookiesдля получения дополнительной информации.
-
Client.session -
Объект, подобный словарю, содержащий информацию о сессии. Смотрите документацию по сессиям для получения полной информации.
Для изменения сессии и сохранения ее, нужно сначала сохранить ее в переменной (потому что новый
SessionStoreсоздается каждый раз при обращении к этому свойству):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка
При тестировании приложений, поддерживающих интернационализацию и локализованность, вам, возможно, потребуется установить язык для запроса тестового клиента. Способ зависит от того, включен ли LocaleMiddleware.
Если middleware включен, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив заголовок Accept-Language HTTP в запросе:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Как Django определяет предпочтение языка.
Если middleware не включен, активный язык может быть установлен с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Явное задание активного языка.
Пример
Ниже приведен простой модульный тест с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См. также
Предоставленные классы тестов
Обычные классы Python тестов расширяют базовый класс unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Преобразование обычного unittest.TestCase в любой из подклассов простое: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные функции тестирования Python будут доступны, и они будут дополнены полезными дополнениями, как описано в каждом разделе ниже.
SimpleTestCase
-
class SimpleTestCase[source]
Подкласс unittest.TestCase, добавляющий следующие возможности:
- Некоторые полезные утверждения, такие как:
- Проверка того, что вызываемый объект
raises a certain exception. - Тестирование поля формы
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. - Пропуск тестов в зависимости от особенностей бэкэнда базы данных skipping based on database backend features.
- Остальные специализированные методы
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 во время тестов.
В более старых версиях Django использовался предварительно определенный диапазон портов, который можно было настроить различными способами, включая переменную среды DJANGO_LIVE_TEST_SERVER_ADDRESS. Это удалено в пользу более простого метода «привязка к порту 0».
Чтобы продемонстрировать, как использовать LiveServerTestCase, давайте напишем простой тест Selenium. Прежде всего, вам необходимо установить пакет selenium в свой путь Python:
$ 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
Этот пример автоматически откроет 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-адресу. Для настройки URLconf используйте декоратор @override_settings(ROOT_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().
Предупреждение
Файл настроек содержит некоторые настройки, которые используются только во время инициализации внутренних компонентов 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(), пользовательский класс тестов 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.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):
...
Затем вы можете выбрать тесты для запуска. Например, чтобы запустить только быстрые тесты:
$ ./manage.py test --tag=fast
Или чтобы запустить быстрые тесты и основной (даже если он медленный):
$ ./manage.py test --tag=fast --tag=core
Вы также можете исключить тесты по тегу. Для запуска тестов core, если они не медленные:
$ ./manage.py test --tag=core --exclude-tag=slow
test --exclude-tag имеет приоритет над test --tag, поэтому, если у теста есть два тега и вы выбираете один из них и исключаете другой, тест не будет запущен.
Сервисы электронной почты
Если какие-либо ваши представления Django отправляют электронные письма с использованием функциональности отправки почты Django, вам, вероятно, не захочется отправлять письма каждый раз, когда вы запускаете тест с использованием этого представления. По этой причине исполняющий файл Django автоматически перенаправляет все письма Django, отправленные Django, в буфер обмена. Это позволяет вам протестировать все аспекты отправки электронных писем – от количества отправленных сообщений до содержимого каждого сообщения – без фактической отправки сообщений.
Исполняющий файл достигает этого, прозрачно заменяя обычный бэкенд электронной почты бэкендом тестирования. (Не беспокойтесь – это не влияет на другие отправки почты за пределами Django, такие как почтовый сервер вашего компьютера, если вы его запускаете.)
-
django.core.mail.outbox
Во время выполнения теста каждое исходящее электронное письмо сохраняется в django.core.mail.outbox. Это простой список всех EmailMessage экземпляров, которые были отправлены. Атрибут outbox – это специальный атрибут, который создаётся только при использовании бэкенда электронной почты locmem. Обычно он не существует в составе модуля 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.0/topics/testing/tools/