Инструменты тестирования
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в 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(), чтобы создать нового пользователя с правильно зашифрованным паролем.Изменено в Django 1.10:В предыдущих версиях неактивным пользователям (
is_active=False) не разрешалось войти в систему.
-
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() запросами.
Политики истечения срока действия этих cookies не соблюдаются. Если вы хотите, чтобы cookie истек, либо удалите его вручную, либо создайте новый экземпляр Client (что фактически удалит все cookies).
У тестового клиента есть два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в рамках условия теста.
-
Объект Python
SimpleCookie, содержащий текущие значения всех cookie клиента. См. документацию модуляhttp.cookiesдля получения более подробной информации.
-
Client.session -
Объект, подобный словарю, содержащий информацию о сессии. См. документацию по сессиям для получения полной информации.
Чтобы изменить сессию и сохранить её, необходимо сначала сохранить её в переменную (так как каждый раз при доступе к этому свойству создаётся новая
SessionStore):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка
При тестировании приложений, поддерживающих международные и локальные настройки, вы можете установить язык для запроса тестового клиента. Способ зависит от того, включен ли LocaleMiddleware.
Если middleware включен, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив заголовок Accept-Language HTTP в запросе:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Как Django определяет предпочтительный язык.
Если middleware не включен, активный язык можно установить с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Дополнительные сведения см. в Явное назначение активного языка.
Пример
Ниже приведен простой юнит-тест, использующий тестовый клиент:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См. также
Предоставленные классы тестовых случаев
Обычные классы юнит-тестов Python наследуют базовый класс unittest.TestCase. Django предоставляет несколько расширений этого базового класса:
Преобразование обычного unittest.TestCase в любой из подклассов легко: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные функциональные возможности Python unit test будут доступны, и они будут дополнены некоторыми полезными дополнениями, как описано в каждом разделе ниже.
SimpleTestCase
-
class SimpleTestCase[source]
Подкласс unittest.TestCase, добавляющий следующие возможности:
- Полезные утверждения, такие как:
- Проверка вызова callable с сообщением
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.
- Проверка вызова callable с сообщением
- Возможность запуска тестов с изменёнными настройками.
- Использование
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(MyTestCase, cls).setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super(MyTestCase, cls).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, будут сохраняться между методами тестов. Если вам нужно их изменить, вы можете перезагрузить их в методе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(MySeleniumTests, cls).setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(MySeleniumTests, cls).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. Этот клиент пересоздаётся для каждого теста, поэтому вам не нужно беспокоиться о сохранении состояния (например, куки) от одного теста к другому.
Это означает, что вместо создания экземпляра 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 TestCase, Client
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 testFluffyAnimals(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 | Стандартное хранилище файлов |
Очистка тестового ящика
Если вы используете любые пользовательские классы Django TestCase , тестовый запуск очистит содержимое тестового ящика электронной почты в начале каждого тестового случая.
Для получения более подробной информации об email-службах во время тестов см. раздел Email services ниже.
Утверждения
Поскольку стандартный класс 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')Устарело начиная с версии 1.9: Передача
callableв качестве ключевого аргумента с именемcallable_objустарела. Вместо этого передайте вызываемый объект в качестве позиционного аргумента.
-
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используется для сравнения.Deprecated since version 1.9: Аргумент
hostустарел, так как перенаправления больше не принудительно делают абсолютными 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
Вы также можете исключить тесты по тегу. Чтобы запустить базовые тесты, если они не медленные:
$ ./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 — это специальный атрибут, созданный только при использовании бэкэнда электронной почты 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 django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
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/1.11/topics/testing/tools/