Spec-Zone.ru › Django 2.2

Инструменты тестирования

Django предоставляет небольшой набор инструментов, которые могут пригодиться при написании тестов.

Клиент тестирования

Клиент тестирования — это класс Python, который действует как эмуляция веб-браузера, позволяющий тестировать ваши представления и взаимодействовать с вашим приложением Django программно.

Некоторые из действий, которые можно выполнить с помощью клиента тестирования:

  • Эмулировать запросы GET и POST на URL и наблюдать за ответом — от низкоуровневого HTTP (заголовки результатов и коды состояния) до содержимого страницы.
  • Просмотреть цепочку редиректов (если таковые имеются) и проверить URL и код состояния на каждом шаге.
  • Проверить, что заданный запрос отображается заданным шаблоном Django с контекстом шаблона, содержащим определённые значения.

Обратите внимание, что клиент тестирования не предназначен для замены Selenium или других фреймворков «в браузере». Клиент тестирования Django имеет другую цель. Короче говоря:

  • Используйте клиент тестирования Django, чтобы убедиться, что отображается правильный шаблон и что шаблону передаются правильные данные контекста.
  • Используйте фреймворки в браузере, такие как Selenium, для тестирования отображённого HTML и поведения веб-страниц, а именно функциональности JavaScript. Django также предоставляет специальную поддержку для этих фреймворков; см. раздел LiveServerTestCase для получения более подробной информации.

Комплектный набор тестов должен использовать комбинацию обоих типов тестов.

Обзор и быстрый пример

Для использования клиента тестирования создайте экземпляр django.test.Client и получите веб-страницы:

>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'

Как показывает этот пример, вы можете создать экземпляр Client внутри сеанса интерпретатора Python.

Обратите внимание на несколько важных моментов, касающихся работы клиента тестирования:

  • Клиент тестирования не требует, чтобы веб-сервер был запущен. На самом деле, он будет работать нормально и без запуска веб-сервера! Это потому, что он избегает накладных расходов HTTP и работает непосредственно с фреймворком Django. Это помогает ускорить выполнение модульных тестов.
  • При получении страниц помните, что нужно указывать путь к URL, а не весь домен. Например, это правильно:

    >>> c.get('/login/')
    

    Это неправильно:

    >>> c.get('https://www.example.com/login/')
    

    Клиент тестирования не может получать веб-страницы, которые не работают на вашем проекте Django. Если вам нужно получить другие веб-страницы, используйте модуль стандартной библиотеки Python, такой как urllib.

  • Для разрешения URL-адресов клиент тестирования использует URLconf, на который указывает ваше значение ROOT_URLCONF.
  • Хотя приведенный выше пример будет работать в интерактивном интерпретаторе Python, некоторые возможности клиента тестирования, особенно связанные с шаблонами, доступны только во время выполнения тестов.

    Причина в том, что инструмент запуска тестов Django выполняет некоторую «магию», чтобы определить, какой шаблон был загружен данным представлением. Эта «магия» (по существу, подмена системы шаблонов Django в памяти) происходит только во время запуска тестов.

  • По умолчанию клиент тестирования отключит любые проверки CSRF, выполняемые вашим сайтом.

    Если по какой-то причине вы хотите, чтобы клиент тестирования выполнял проверки CSRF, вы можете создать экземпляр клиента тестирования, который выполняет проверки CSRF. Для этого передайте аргумент enforce_csrf_checks при создании вашего клиента:

    >>> from django.test import Client
    >>> csrf_client = Client(enforce_csrf_checks=True)
    

Выполнение запросов

Используйте класс django.test.Client для выполнения запросов.

class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults) [source]

Он не требует аргументов при создании. Однако вы можете использовать ключевые аргументы для указания некоторых стандартных заголовков. Например, это отправит заголовок User-Agent в каждом запросе:

>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')

Значения из ключевых аргументов extra, переданных в get(), post() и т.д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.

Аргумент enforce_csrf_checks может использоваться для проверки защиты от CSRF (см. выше).

Аргумент json_encoder позволяет установить пользовательский кодировщик JSON для сериализации JSON, описанной в post().

Изменено в Django 2.1:

Добавлен аргумент json_encoder.

После получения экземпляра Client, вы можете вызвать любой из следующих методов:

get(path, data=None, follow=False, secure=False, **extra) [source]

Выполняет GET-запрос на указанный path и возвращает объект Response, документация которого приведена ниже.

Ключевые пары в словаре data используются для создания данных GET-запроса. Например:

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})

…приведёт к выполнению GET-запроса, эквивалентного:

/customers/details/?name=fred&age=7

Ключевые аргументы extra могут использоваться для указания заголовков, которые должны быть отправлены в запросе. Например:

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
...       HTTP_X_REQUESTED_WITH='XMLHttpRequest')

…отправит HTTP-заголовок HTTP_X_REQUESTED_WITH в представление деталей, что является хорошим способом проверки ветвей кода, использующих метод django.http.HttpRequest.is_ajax().

Спецификация CGI

Заголовки, отправленные через **extra, должны соответствовать спецификации CGI. Например, имитация заголовка «Host» при отправке HTTP-запроса браузером на сервер должна передаваться как HTTP_HOST.

Если у вас уже есть GET-аргументы в URL-кодированном формате, вы можете использовать это кодирование вместо аргумента data. Например, предыдущий GET-запрос также можно задать как:

>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')

Если вы предоставите URL с кодированными данными GET и аргументом data, аргумент data будет иметь приоритет.

Если вы установите follow в True, клиент будет следовать любым редиректам, и в объекте ответа будет установлен атрибут redirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.

Если у вас был URL /redirect_me/, который перенаправлял на /next/, который перенаправлял на /final/, вот что вы увидите:

>>> response = c.get('/redirect_me/', follow=True)
>>> response.redirect_chain
[('http://testserver/next/', 302), ('http://testserver/final/', 302)]

Если вы установите secure в True, клиент будет имитировать HTTPS-запрос.

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra) [source]

Выполняет POST-запрос на указанный path и возвращает объект Response, документация которого приведена ниже.

Ключевые пары в словаре data используются для отправки данных POST. Например:

>>> c = Client()
>>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})

…приведёт к выполнению POST-запроса к этому URL:

/login/

…с этими данными POST:

name=fred&passwd=secret

Если вы укажите content_type как application/json, data сериализуется с помощью json.dumps(), если это словарь, список или кортеж. Сериализация выполняется с помощью DjangoJSONEncoder по умолчанию и может быть переопределена путём предоставления аргумента json_encoder в Client. Эта сериализация также происходит для запросов put(), patch() и delete().

Изменено в Django 2.1:

Была добавлена описанная выше сериализация JSON. В более старых версиях вы можете вызвать json.dumps() на data перед передачей его в post(), чтобы добиться того же результата.

Изменено в Django 2.2:

Сериализация JSON была расширена для поддержки списков и кортежей. В более старых версиях сериализовывались только словари.

Если вы предоставите другой 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')}

Загрузка файлов — это особый случай. Чтобы отправить файл POST, вам нужно указать только имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например:

>>> c = Client()
>>> with open('wishlist.doc') as fp:
...     c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})

(Имя attachment здесь не имеет значения; используйте любое имя, ожидаемое вашим кодом обработки файлов.)

Вы также можете предоставить любой объект типа файла (например, StringIO или BytesIO) в качестве дескриптора файла. Если вы загружаете файл в ImageField, объект должен иметь атрибут name, который передает валидатор validate_image_file_extension. Например:

>>> from io import BytesIO
>>> img = BytesIO(b'mybinarydata')
>>> img.name = 'myimage.jpg'

Обратите внимание, что если вы хотите использовать один и тот же дескриптор файла для нескольких вызовов post() , вам нужно будет вручную сбросить указатель файла между отправками. Самый простой способ сделать это — вручную закрыть файл после того, как он был предоставлен post(), как показано выше.

Также убедитесь, что файл открыт таким образом, чтобы данные могли быть прочитаны. Если ваш файл содержит двоичные данные, такие как изображение, это означает, что вам нужно открыть файл в режиме rb (чтение двоичных данных).

Аргумент extra действует так же, как для Client.get().

Если URL, который вы запрашиваете с помощью POST, содержит закодированные параметры, эти параметры будут доступны в данных request.GET. Например, если вы сделаете запрос:

>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})

…представление, обрабатывающее этот запрос, может получить имя пользователя и пароль с помощью request.POST и определить, является ли пользователь посетителем с помощью request.GET.

Если вы установите follow в True, клиент будет следовать любым редиректам, и в объекте ответа будет установлен атрибут redirect_chain, содержащий кортежи промежуточных URL-адресов и кодов состояния.

Если вы установите secure в True, клиент будет имитировать HTTPS-запрос.

head(path, data=None, follow=False, secure=False, **extra) [source]

Выполняет запрос HEAD на предоставленный path и возвращает объект Response. Этот метод работает так же, как Client.get(), включая аргументы follow, secure и extra, за исключением того, что он не возвращает тело сообщения.

options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) [source]

Выполняет запрос OPTIONS на указанный path и возвращает объект Response. Полезно для тестирования RESTful-интерфейсов.

Когда data предоставлен, он используется в качестве тела запроса, и заголовок Content-Type устанавливается в content_type.

Аргументы follow, secure и extra действуют так же, как для Client.get().

put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) [source]

Выполняет запрос PUT к предоставленному path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

Если предоставлен data, он используется в качестве тела запроса, и заголовок Content-Type устанавливается в значение content_type.

Аргументы follow, secure и extra работают так же, как и для Client.get().

patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) [source]

Выполняет запрос PATCH к предоставленному path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

Аргументы follow, secure и extra работают так же, как и для Client.get().

delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) [source]

Выполняет запрос DELETE к предоставленному path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

Если предоставлен data, он используется в качестве тела запроса, и заголовок Content-Type устанавливается в значение content_type.

Аргументы follow, secure и extra работают так же, как и для Client.get().

trace(path, follow=False, secure=False, **extra) [source]

Выполняет запрос TRACE к предоставленному path и возвращает объект Response. Полезно для имитации диагностических зондирований.

В отличие от других методов запроса, data не предоставляется в качестве ключевого параметра для соответствия RFC 7231#section-4.3.8, который предписывает, что запросы TRACE не должны иметь тело.

Аргументы follow, secure, и extra работают так же, как и для Client.get().

login(**credentials) [source]

Если ваш сайт использует систему аутентификации Django, и вам нужно имитировать вход пользователя, вы можете использовать метод login() тестового клиента для этого.

После вызова этого метода, тестовый клиент будет содержать все необходимые куки и данные сеанса для прохождения любых тестов, связанных с входом в систему.

Формат аргумента credentials зависит от используемого вами аутентификационного бэкенда (настроенного вашим параметром AUTHENTICATION_BACKENDS). Если вы используете стандартный аутентификационный бэкенд Django (ModelBackend), credentials должны быть именем пользователя и паролем в качестве ключевых аргументов:

>>> c = Client()
>>> c.login(username='fred', password='secret')

# Now you can access a view that's only available to logged-in users.

Если вы используете другой аутентификационный бэкенд, для этого метода могут потребоваться другие учетные данные. Он требует тех учетных данных, которые необходимы методу authenticate() вашего бэкенда.

login() возвращает True если учетные данные были приняты и вход выполнен успешно.

В конечном счёте, вам нужно будет создать учётные записи пользователей, прежде чем вы сможете использовать этот метод. Как мы уже объяснили, тестовый запуск выполняется с использованием тестовой базы данных, которая по умолчанию не содержит пользователей. В результате учётные записи пользователей, действующие на вашем производственном сайте, не будут работать в тестовых условиях. Вам нужно создать пользователей в рамках набора тестов — либо вручную (используя API модели Django), либо с помощью фикстуры теста. Помните, что если вы хотите, чтобы у вашего тестового пользователя был пароль, вы не можете установить пароль пользователя, установив атрибут password напрямую — вы должны использовать функцию set_password() для хранения правильно хешированного пароля. В качестве альтернативы вы можете использовать вспомогательный метод create_user() для создания нового пользователя с правильно хешированным паролем.

force_login(user, backend=None) [source]

Если ваш сайт использует систему аутентификации Django, вы можете использовать метод force_login() для имитации входа пользователя в систему. Используйте этот метод вместо login() в тех случаях, когда тест требует войти в систему, а детали того, как пользователь вошел, не важны.

В отличие от login(), этот метод пропускает шаги аутентификации и проверки: неактивные пользователи (is_active=False) могут войти в систему, и учетные данные пользователя не нужны.

У пользователя будет установлено атрибут backend в значение аргумента backend (который должен быть строкой с точечной записью Python), или в значение settings.AUTHENTICATION_BACKENDS[0] если значение не указано. Функция authenticate(), вызываемая методом login(), обычно маркирует пользователя таким образом.

Этот метод быстрее, чем login(), так как обходятся дорогостоящие алгоритмы хеширования паролей. Также вы можете ускорить login() с помощью использования более слабого хешера во время тестирования.

logout() [source]

Если ваш сайт использует систему аутентификации Django, метод logout() может быть использован для имитации выхода пользователя из вашего сайта.

После вызова этого метода, у тестового клиента будут очищены все куки и данные сеанса до значений по умолчанию. Последующие запросы будут поступать от AnonymousUser.

Тестирование ответов

Методы get() и post() оба возвращают объект Response. Этот объект Response не совпадает с объектом HttpResponse возвращаемым представлениями Django; тестовый объект ответа содержит некоторые дополнительные данные, полезные для кода теста для проверки.

В частности, объект Response имеет следующие атрибуты:

class Response
client

Тестовый клиент, который был использован для отправки запроса, приведшего к ответу.

content

Тело ответа в виде байтовой строки. Это окончательное содержимое страницы, отрендеренное представлением, или любое сообщение об ошибке.

context

Экземпляр шаблона Context , который был использован для рендеринга шаблона, породившего содержимое ответа.

Если для рендеринга страницы использовалось несколько шаблонов, то context будет списком объектов Context, в порядке их рендеринга.

Независимо от количества использованных шаблонов при рендеринге, вы можете получить значения контекста, используя оператор []. Например, переменная контекста name можно получить так:

>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'

Не используете Django шаблоны?

Этот атрибут заполняется только при использовании бэкенда DjangoTemplates. Если вы используете другой движок шаблонов, context_data может быть подходящей альтернативой для ответов с этим атрибутом.

json(**kwargs)

Тело ответа, распарсенное как JSON. Дополнительные ключевые аргументы передаются в json.loads(). Например:

>>> response = client.get('/foo/')
>>> response.json()['name']
'Arthur'

Если заголовок Content-Type не "application/json", при попытке парсить ответ будет поднята ошибка ValueError.

request

Данные запроса, которые стимулировали ответ.

wsgi_request

Экземпляр WSGIRequest , сгенерированный обработчиком теста, который сгенерировал ответ.

status_code

HTTP статус ответа в виде целого числа. Полный список кодов см. в реестре кодов статуса IANA.

templates

Список экземпляров Template , использованных для рендеринга конечного содержимого в порядке их рендеринга. Для каждого шаблона в списке используйте template.name для получения имени файла шаблона, если шаблон загружен из файла. (Имя - это строка, например, 'admin/index.html'.)

Не используете Django шаблоны?

Этот атрибут заполняется только при использовании бэкенда DjangoTemplates. Если вы используете другой движок шаблонов, template_name может быть подходящей альтернативой, если вам нужно только имя шаблона, использованного для рендеринга.

resolver_match

Экземпляр ResolverMatch для ответа. Вы можете использовать атрибут func, например, чтобы проверить представление, которое обслужило ответ:

# my_view here is a function based view
self.assertEqual(response.resolver_match.func, my_view)

# class-based views need to be compared by name, as the functions
# generated by as_view() won't be equal
self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__)

Если указанный URL не найден, доступ к этому атрибуту вызовет исключение Resolver404.

Вы также можете использовать синтаксис словаря для объекта ответа, чтобы получить значение любого параметра в заголовках HTTP. Например, вы можете определить тип содержимого ответа, используя response['Content-Type'].

Исключения

Если вы направите тестовый клиент на представление, которое поднимает исключение, это исключение будет видно в тестовом случае. Затем вы можете использовать стандартный блок try ... except или assertRaises() для проверки исключений.

Единственные исключения, которые не видны тестовому клиенту, это Http404, PermissionDenied, SystemExit и SuspiciousOperation. Django обрабатывает эти исключения внутри и преобразует их в соответствующие коды ответов HTTP. В этих случаях вы можете проверить response.status_code в своём тесте.

Состояние

Тестовый клиент имеет состояние. Если ответ возвращает cookie, то эта cookie будет храниться в тестовом клиенте и отправляться со всеми последующими запросами get() и post().

Политики истечения срока действия для этих cookie не соблюдаются. Если вы хотите, чтобы cookie истекла, либо удалите её вручную, либо создайте новый экземпляр Client (что фактически удалит все cookie).

Тестовый клиент имеет два атрибута, которые хранят информацию о состоянии. Вы можете получить доступ к этим свойствам в качестве части условия теста.

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)

См. также

django.test.RequestFactory

Предоставляемые классы тестовых случаев

Обычные классы юнит-тестов Python наследуют базовый класс unittest.TestCase. Django предоставляет несколько расширений этого базового класса:

Hierarchy of Django unit testing classes (TestCase subclasses)

Иерархия классов юнит-тестов Django

Преобразование обычного unittest.TestCase в любой из подклассов простое: измените базовый класс вашего теста с unittest.TestCase на подкласс. Все стандартные возможности юнит-тестов Python будут доступны, и они будут дополнены некоторыми полезными дополнениями, как описано в каждом разделе ниже.

SimpleTestCase

class SimpleTestCase [source]

Подкласс unittest.TestCase, добавляющий следующие возможности:

  • Полезные утверждения, такие как:
    • Проверка вызова метода с сообщением raises a certain exception.
    • Проверка вызова метода с предупреждением triggers a certain warning.
    • Тестирование вывода поля формы rendering and error treatment.
    • Тестирование HTML responses for the presence/lack of a given fragment.
    • Проверка использования шаблона has/hasn't been used to generate a given response content.
    • Проверка равенства двух URLs.
    • Проверка перенаправления HTTP redirect приложением.
    • Надежное тестирование двух HTML fragments на равенство/неравенство или containment.
    • Надежное тестирование двух XML fragments на равенство/неравенство.
    • Надежное тестирование двух JSON fragments на равенство.
  • Возможность запуска тестов с измененными настройками.
  • Использование client Client.

Если ваши тесты выполняют запросы к базе данных, используйте подклассы TransactionTestCase или TestCase.

SimpleTestCase.databases
Новое в Django 2.2.

SimpleTestCase по умолчанию запрещает запросы к базе данных. Это помогает избежать выполнения записывающих запросов, которые могут повлиять на другие тесты, так как каждый SimpleTestCase тест не выполняется в транзакции. Если вас это не беспокоит, вы можете отключить это поведение, установив атрибут класса databases в '__all__' для вашего класса тестов.

SimpleTestCase.allow_database_queries

Устарело начиная с версии 2.2.

Этот атрибут устарел в пользу databases. Предыдущее поведение allow_database_queries = True можно получить, установив databases = '__all__'.

Предупреждение

SimpleTestCase и его подклассы (например, TestCase, …) полагаются на setUpClass() и tearDownClass() для выполнения некоторых инициализаций на уровне класса (например, переопределение настроек). Если вам нужно переопределить эти методы, не забудьте вызвать реализацию super:

class MyTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        ...

    @classmethod
    def tearDownClass(cls):
        ...
        super().tearDownClass()

Учитывайте поведение Python, если во время setUpClass() возникает исключение. В этом случае ни тесты в классе, ни tearDownClass() не выполняются. В случае django.test.TestCase это приведет к утечке транзакции, созданной в super(), что может привести к различным проблемам, включая ошибку сегментации на некоторых платформах (сообщалось на macOS). Если вы хотите намеренно вызвать исключение, такое как unittest.SkipTest в setUpClass(), обязательно сделайте это до вызова super() для предотвращения этой проблемы.

TransactionTestCase

class TransactionTestCase [source]

TransactionTestCase наследуется от SimpleTestCase, добавляя некоторые возможности, специфичные для базы данных:

  • Сброс базы данных до известного состояния в начале каждого теста для облегчения тестирования и использования ORM.
  • Фикстуры базы данных fixtures.
  • Пропуск тестов в зависимости от функций бэкенда базы данных пропуск тестов.
  • Остальные специализированные методы assert*.

Класс TestCase Django — более часто используемый подкласс TransactionTestCase, который использует возможности транзакций базы данных для ускорения процесса сброса базы данных до известного состояния в начале каждого теста. Однако следствием этого является то, что некоторые поведения базы данных нельзя протестировать в классе Django TestCase. Например, вы не можете проверить, что блок кода выполняется в рамках транзакции, как это требуется при использовании select_for_update(). В таких случаях следует использовать TransactionTestCase.

TransactionTestCase и TestCase идентичны, за исключением способа сброса базы данных до известного состояния и возможности тестирования эффектов commit и rollback:

  • Класс TransactionTestCase сбрасывает базу данных после выполнения теста, обнуляя все таблицы. Класс TransactionTestCase может вызывать commit и rollback и наблюдать за эффектами этих вызовов на базе данных.
  • Класс TestCase, с другой стороны, не обнуляет таблицы после теста. Вместо этого он заключает код теста в транзакцию базы данных, которая откатывается в конце теста. Это гарантирует, что откат в конце теста восстанавливает базу данных до первоначального состояния.

Предупреждение

TestCase, работающий на базе данных, которая не поддерживает откат (например, MySQL с движком хранения MyISAM), и все экземпляры TransactionTestCase, откатываются в конце теста путем удаления всех данных из тестовой базы данных.

Приложения не увидят перезагрузку своих данных; если вам нужна эта функциональность (например, сторонние приложения должны это включить), вы можете установить serialized_rollback = True внутри блока TestCase.

TestCase

class TestCase [source]

Это наиболее распространенный класс для написания тестов в Django. Он наследуется от TransactionTestCase (и, следовательно, от SimpleTestCase). Если ваше приложение Django не использует базу данных, используйте SimpleTestCase.

Класс:

  • Оборачивает тесты в два вложенных блока atomic(): один для всего класса и один для каждого теста. Поэтому, если вы хотите проверить определенное поведение транзакций базы данных, используйте TransactionTestCase.
  • Проверяет откладываемые ограничения базы данных в конце каждого теста.

Он также предоставляет дополнительный метод:

classmethod TestCase.setUpTestData() [source]

Блок уровня класса atomic, описанный выше, позволяет создавать начальные данные на уровне класса один раз для всего TestCase. Этот метод позволяет ускорить тесты по сравнению с использованием setUp().

Например:

from django.test import TestCase

class MyTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        # Set up data for the whole TestCase
        cls.foo = Foo.objects.create(bar="Test")
        ...

    def test1(self):
        # Some test using self.foo
        ...

    def test2(self):
        # Some other test using self.foo
        ...

Обратите внимание, что если тесты выполняются на базе данных без поддержки транзакций (например, MySQL с движком MyISAM), setUpTestData() будет вызываться перед каждым тестом, что снижает преимущества скорости.

Будьте внимательны, не изменяйте объекты, созданные в setUpTestData() в методах ваших тестов. Изменения в объектах из оперативной памяти, выполненные на уровне класса, сохраняются между методами тестов. Если вам необходимо их изменить, вы можете перезагрузить их в методе setUp() с помощью refresh_from_db(), например.

LiveServerTestCase

class LiveServerTestCase [source]

LiveServerTestCase делает в основном то же самое, что и TransactionTestCase, с одной дополнительной функцией: он запускает активный сервер Django на заднем плане при настройке и закрывает его при завершении. Это позволяет использовать автоматизированных клиентских тестов, помимо Django dummy client, таких как, например, клиент Selenium, для выполнения серии функциональных тестов внутри браузера и моделирования действий реального пользователя.

Активный сервер прослушивает localhost и связывается с портом 0, который использует свободный порт, назначенный операционной системой. К URL-адресу сервера можно получить доступ с помощью self.live_server_url во время тестирования.

Чтобы продемонстрировать, как использовать LiveServerTestCase, давайте напишем простой тест Selenium. Прежде всего, вам необходимо установить пакет selenium в ваш путь Python:

$ pip install selenium
...\> pip install selenium

Затем добавьте тест на основе LiveServerTestCase в модуль тестов вашего приложения (например: myapp/tests.py). В этом примере мы будем предполагать, что вы используете приложение staticfiles и хотите иметь статические файлы, которые обрабатываются во время выполнения ваших тестов, аналогично тому, как мы получаем их на этапе разработки с DEBUG=True, то есть без необходимости их сборки с помощью collectstatic. Мы будем использовать подкласс StaticLiveServerTestCase, который предоставляет эту функциональность. Замените его на django.test.LiveServerTestCase если вам это не нужно.

Код для этого теста может выглядеть следующим образом:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver

class MySeleniumTests(StaticLiveServerTestCase):
    fixtures = ['user-data.json']

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.selenium = WebDriver()
        cls.selenium.implicitly_wait(10)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super().tearDownClass()

    def test_login(self):
        self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
        username_input = self.selenium.find_element_by_name("username")
        username_input.send_keys('myuser')
        password_input = self.selenium.find_element_by_name("password")
        password_input.send_keys('secret')
        self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()

Наконец, вы можете запустить тест следующим образом:

$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login

Этот пример автоматически откроет Firefox, перейдет на страницу входа, введет учетные данные и нажмет кнопку «Войти». Selenium предлагает другие драйверы на случай, если у вас не установлен Firefox или вы хотите использовать другой браузер. Приведенный выше пример — лишь малая часть того, что может сделать клиент Selenium; ознакомьтесь с полной справкой для получения более подробной информации.

Примечание

При использовании в памяти SQLite базы данных для запуска тестов, одно и то же подключение к базе данных будет использоваться двумя потоками параллельно: потоком, в котором запускается активный сервер, и потоком, в котором выполняется тест. Важно предотвратить одновременные запросы к базе данных через это общее подключение двумя потоками, так как это иногда случайно приводит к сбоям тестов. Поэтому необходимо убедиться, что оба потока не обращаются к базе данных одновременно. В частности, это означает, что в некоторых случаях (например, сразу после щелчка по ссылке или отправки формы) вам может потребоваться проверить, получен ли ответ Selenium и загружена ли следующая страница, прежде чем продолжать дальнейшее выполнение теста. Сделайте это, например, заставив Selenium ждать, пока тег <body> HTML не будет найден в ответе (требуется 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 test client. К этому клиенту можно получить доступ как self.client. Этот клиент пересоздается для каждого теста, поэтому вам не нужно беспокоиться о том, что состояние (например, куки) будет переноситься от одного теста к другому.

Это означает, что вместо создания экземпляра Client в каждом тесте:

import unittest
from django.test import Client

class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        client = Client()
        response = client.get('/customer/index/')
        self.assertEqual(response.status_code, 200)

…вы можете просто обратиться к self.client, как показано ниже:

from django.test import TestCase

class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get('/customer/index/')
        self.assertEqual(response.status_code, 200)

Настройка клиента тестов

SimpleTestCase.client_class

Если вы хотите использовать другой класс Client (например, подкласс с настраиваемым поведением), используйте атрибут класса client_class:

from django.test import Client, TestCase

class MyTestClient(Client):
    # Specialized methods for your environment
    ...

class MyTest(TestCase):
    client_class = MyTestClient

    def test_my_stuff(self):
        # Here self.client is an instance of MyTestClient...
        call_some_test_code()

Загрузка фикстур

TransactionTestCase.fixtures

Тест-кейс для веб-сайта на базе данных мало пригоден, если в базе данных нет данных. Тесты более читабельны и удобнее поддерживать, если создавать объекты с помощью ORM, например, в TestCase.setUpTestData(), однако вы также можете использовать фикстуры.

Фикстура — это набор данных, которые Django знает, как импортировать в базу данных. Например, если ваш сайт имеет учетные записи пользователей, вы можете создать фикстуру с поддельными учетными записями пользователей, чтобы заполнить вашу базу данных во время тестов.

Самый простой способ создания фикстуры — использование команды manage.py dumpdata. Это предполагает, что у вас уже есть данные в вашей базе данных. Подробнее см. dumpdata documentation.

После создания фикстуры и размещения её в каталоге fixtures в одном из ваших INSTALLED_APPS, вы можете использовать её в ваших модульных тестах, указав атрибут класса fixtures в вашем подклассе django.test.TestCase:

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    fixtures = ['mammals.json', 'birds']

    def setUp(self):
        # Test definitions as before.
        call_setup_methods()

    def test_fluffy_animals(self):
        # A test that uses the fixtures.
        call_some_test_code()

Вот что произойдёт:

  • В начале каждого теста, прежде чем выполниться setUp(), Django очистит базу данных, вернув базу данных в состояние, в котором она находилась непосредственно после вызова migrate.
  • Затем устанавливаются все указанные фикстуры. В этом примере Django установит все JSON фикстуры, имеющие имя mammals, за которыми следуют фикстуры с именем birds. См. документацию loaddata для получения более подробной информации о определении и установке фикстур.

По соображениям производительности, TestCase загружает фикстуры один раз для всего класса тестов, перед setUpTestData(), а не перед каждым тестом, и использует транзакции для очистки базы данных перед каждым тестом. В любом случае, можно быть уверенным, что результат теста не будет зависеть от другого теста или от порядка выполнения тестов.

По умолчанию, фикстуры загружаются только в базу данных default. Если вы используете несколько баз данных и установили TransactionTestCase.databases, фикстуры будут загружены во все указанные базы данных.

Настройка URLconf

Если ваше приложение предоставляет представления, вы можете захотеть включить тесты, которые используют клиент тестов для проверки этих представлений. Однако конечный пользователь свободен развернуть представления вашего приложения в любом URL-адресе по своему выбору. Это означает, что ваши тесты не могут полагаться на то, что ваши представления будут доступны по конкретному URL-адресу. Используйте декоратор @override_settings(ROOT_URLCONF=...) для настройки URLconf.

Поддержка нескольких баз данных

TransactionTestCase.databases
Новая функция в Django 2.2.

Django настраивает тестовую базу данных, соответствующую каждой базе данных, определённой в определении DATABASES в ваших настройках и упоминаемой хотя бы одним тестом через databases.

Однако большая часть времени, затрачиваемого на выполнение Django TestCase , расходуется на вызов flush , который гарантирует, что база данных чистая в начале каждого запуска тестов. При наличии нескольких баз данных требуется несколько сбросов (по одному для каждой базы данных), что может быть длительной операцией — особенно если ваши тесты не нуждаются в проверке работы с несколькими базами данных.

В целях оптимизации Django сбрасывает только базу данных default в начале каждого запуска тестов. Если ваша настройка содержит несколько баз данных, и у вас есть тест, который требует, чтобы все базы данных были чистыми, вы можете использовать атрибут databases в наборе тестов, чтобы запросить сброс дополнительных баз данных.

Например:

class TestMyViews(TransactionTestCase):
    databases = {'default', 'other'}

    def test_index_page_view(self):
        call_some_test_code()

Этот тест сбросит базы данных default и other перед запуском test_index_page_view. Вы также можете использовать '__all__' , чтобы указать, что необходимо сбросить все тестовые базы данных.

Флаг databases также управляет тем, в какие базы данных загружаются TransactionTestCase.fixtures. По умолчанию фикстуры загружаются только в базу данных default.

Запросы к базам данных, не входящим в databases , будут вызывать ошибки утверждения для предотвращения утечки состояния между тестами.

TransactionTestCase.multi_db

Устарело начиная с версии 2.2.

Этот атрибут устарел в пользу databases. Предыдущее поведение multi_db = True можно получить, установив databases = '__all__'.

TestCase.databases
Добавлен в Django 2.2.

По умолчанию только база данных default будет обернута транзакцией во время выполнения TestCase , и попытки запроса к другим базам данных приведут к ошибкам утверждения, чтобы предотвратить утечку состояния между тестами.

Используйте атрибут класса databases в тестовом классе, чтобы запросить оборачивание транзакцией для баз данных, отличных от default.

Например:

class OtherDBTests(TestCase):
    databases = {'other'}

    def test_other_db_query(self):
        ...

Этот тест позволит выполнять запросы только к базе данных other. Так же, как и для SimpleTestCase.databases и TransactionTestCase.databases, константа '__all__' может использоваться для указания, что тест должен разрешать запросы ко всем базам данных.

TestCase.multi_db

Устарело начиная с версии 2.2.

Этот атрибут устарел в пользу databases. Предыдущее поведение multi_db = True можно получить, установив databases = '__all__'.

Переопределение настроек

Предупреждение

Используйте приведенные ниже функции для временного изменения значения настроек в тестах. Не изменяйте django.conf.settings напрямую, так как Django не восстановит исходные значения после таких манипуляций.

SimpleTestCase.settings() [source]

Для целей тестирования часто бывает полезно временно изменить настройку и вернуть исходное значение после выполнения тестового кода. Для этого Django предоставляет стандартный менеджер контекста Python (см. PEP 343), который называется settings(), и может использоваться следующим образом:

from django.test import TestCase

class LoginTestCase(TestCase):

    def test_login(self):

        # First check for the default behavior
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/accounts/login/?next=/sekrit/')

        # Then override the LOGIN_URL setting
        with self.settings(LOGIN_URL='/other/login/'):
            response = self.client.get('/sekrit/')
            self.assertRedirects(response, '/other/login/?next=/sekrit/')

В этом примере будет переопределена настройка LOGIN_URL для кода в блоке with и после этого её значение будет восстановлено.

SimpleTestCase.modify_settings() [source]

Переопределение настроек, содержащих список значений, может оказаться неудобным. На практике часто достаточно добавления или удаления значений. Менеджер контекста modify_settings() упрощает эту задачу:

from django.test import TestCase

class MiddlewareTestCase(TestCase):

    def test_cache_middleware(self):
        with self.modify_settings(MIDDLEWARE={
            'append': 'django.middleware.cache.FetchFromCacheMiddleware',
            'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
            'remove': [
                'django.contrib.sessions.middleware.SessionMiddleware',
                'django.contrib.auth.middleware.AuthenticationMiddleware',
                'django.contrib.messages.middleware.MessageMiddleware',
            ],
        }):
            response = self.client.get('/')
            # ...

Для каждого действия вы можете предоставить либо список значений, либо строку. Когда значение уже существует в списке, append и prepend не оказывают влияния; также remove не оказывает влияния, когда значение не существует.

override_settings() [source]

В случае необходимости переопределения настройки для метода теста Django предоставляет декоратор override_settings() (см. PEP 318). Он используется следующим образом:

from django.test import TestCase, override_settings

class LoginTestCase(TestCase):

    @override_settings(LOGIN_URL='/other/login/')
    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')

Декоратор также может применяться к классам TestCase:

from django.test import TestCase, override_settings

@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):

    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')
modify_settings() [source]

Аналогичным образом Django предоставляет декоратор modify_settings():

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

    @modify_settings(MIDDLEWARE={
        'append': 'django.middleware.cache.FetchFromCacheMiddleware',
        'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
    })
    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

Декоратор также может применяться к классам тестовых случаев:

from django.test import TestCase, modify_settings

@modify_settings(MIDDLEWARE={
    'append': 'django.middleware.cache.FetchFromCacheMiddleware',
    'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
class MiddlewareTestCase(TestCase):

    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

Примечание

При применении к классу эти декораторы изменяют класс напрямую и возвращают его; они не создают и не возвращают изменённую копию. Поэтому, если вы попытаетесь изменить примеры выше, назначив возвращаемое значение другому имени, отличном от LoginTestCase или MiddlewareTestCase, вы можете быть удивлены, обнаружив, что исходные классы тестовых случаев также подвержены влиянию декоратора. Для данного класса modify_settings() всегда применяется после override_settings().

Рекомендации по использованию Python 3.5

Если вы используете Python 3.5 (или более старые версии, если используете более старую версию Django), избегайте смешивания remove с append и prepend в modify_settings(). В некоторых случаях имеет значение, добавляется ли сначала значение, а затем удаляется, или наоборот, а порядок ключей словаря не сохраняется до Python 3.6. Вместо этого применяйте декоратор дважды, чтобы гарантировать порядок операций. Например, чтобы гарантировать, что SessionMiddleware появляется первым в MIDDLEWARE:

@modify_settings(MIDDLEWARE={
    'remove': ['django.contrib.sessions.middleware.SessionMiddleware'],
)
@modify_settings(MIDDLEWARE={
    'prepend': ['django.contrib.sessions.middleware.SessionMiddleware'],
})

Предупреждение

Файл настроек содержит некоторые настройки, которые используются только при инициализации внутренних компонентов Django. Если вы изменяете их с помощью override_settings, настройка изменяется при обращении к ней через модуль django.conf.settings, однако внутренние компоненты Django обращаются к ней по-другому. В результате использование override_settings() или modify_settings() с этими настройками, вероятно, не даст ожидаемого результата.

Мы не рекомендуем изменять настройку DATABASES. Изменение настройки CACHES возможно, но немного сложно, если вы используете внутренние компоненты, которые используют кэширование, например, django.contrib.sessions. Например, вам придётся повторно инициализировать бэкенд сессий в тесте, который использует кэшированные сессии и переопределяет CACHES.

Наконец, избегайте алиасов своих настроек в качестве констант на уровне модуля, так как override_settings() не будет работать с такими значениями, так как они оцениваются только при первом импорте модуля.

Вы также можете симулировать отсутствие настройки, удалив её после переопределения настроек, как показано ниже:

@override_settings()
def test_something(self):
    del settings.LOGIN_URL
    ...

При переопределении настроек убедитесь, что вы обрабатываете случаи, в которых код вашего приложения использует кэш или аналогичную функцию, которая сохраняет состояние, даже если настройка изменена. Django предоставляет сигнал django.test.signals.setting_changed, который позволяет регистрировать обратные вызовы для очистки и иного сброса состояния при изменении настроек.

Сам Django использует этот сигнал для сброса различных данных:

Изменённые настройки Сброс данных
USE_TZ, TIME_ZONE Временная зона баз данных
TEMPLATES Шаблоны
SERIALIZATION_MODULES Кэш сериализаторов
LOCALE_PATHS, LANGUAGE_CODE Стандартный язык и загруженные языки
MEDIA_ROOT, DEFAULT_FILE_STORAGE Стандартное хранилище файлов

Очистка тестового ящика вывода

Если вы используете какие-либо пользовательские классы Django, TestCase, то тестовый прогон очистит содержимое тестового ящика вывода электронных писем в начале каждого тестового случая.

Более подробную информацию о службах электронной почты во время тестов см. ниже в разделе Службы электронной почты.

Утверждения

Поскольку стандартный класс Python unittest.TestCase реализует методы утверждений, такие как assertTrue() и assertEqual(), пользовательский класс Django TestCase предоставляет ряд пользовательских методов утверждения, полезных для тестирования веб-приложений:

Сообщения об ошибках, генерируемые большинством этих методов утверждения, можно настроить с помощью аргумента msg_prefix. Эта строка будет добавленна в начало любого сообщения об ошибке, сгенерированном утверждением. Это позволяет предоставлять дополнительные сведения, которые могут помочь определить местоположение и причину ошибки в вашей тестовой сборке.

SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs) [source]
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)

Утверждает, что выполнение callable вызывает expected_exception, и что expected_message содержится в сообщении об исключении. Любой другой результат считается ошибкой. Это упрощённая версия unittest.TestCase.assertRaisesRegex() с различием, что expected_message не рассматривается как регулярное выражение.

Если заданы только параметры expected_exception и expected_message, возвращается менеджер контекста, так что тестируемый код можно написать в строке, а не как функцию:

with self.assertRaisesMessage(ValueError, 'invalid literal for int()'):
    int('a')
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs) [source]
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
Добавлено в Django 2.1.

Аналогично SimpleTestCase.assertRaisesMessage(), но для assertWarnsRegex() вместо assertRaisesRegex().

SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='') [source]

Утверждает, что поле формы работает корректно с различными входами.

Параметры:
  • fieldclass – класс тестируемого поля.
  • valid – словарь, сопоставляющий корректные входы с ожидаемыми очищенными значениями.
  • invalid – словарь, сопоставляющий некорректные входы с одним или несколькими сообщениями об ошибках.
  • field_args – аргументы, передаваемые при создании поля.
  • field_kwargs – ключевые аргументы, передаваемые при создании поля.
  • empty_value – ожидаемый результат очистки для входов в empty_values.

Например, следующий код проверяет, что EmailField принимает a@a.com как корректный адрес электронной почты, но отклоняет aaa с разумным сообщением об ошибке:

self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix='') [source]

Утверждает, что поле в форме вызывает предоставленный список ошибок при рендеринге в форме.

form - имя экземпляра Form в контексте шаблона.

field - имя поля в форме для проверки. Если field имеет значение None, будут проверены ошибки, не связанные с полем (ошибки, доступные через form.non_field_errors()).

errors - строка ошибки или список строк ошибок, ожидаемых в результате валидации формы.

SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='') [source]

Утверждает, что formset вызывает предоставленный список ошибок при рендеринге.

formset - имя экземпляра Formset в контексте шаблона.

form_index - номер формы в Formset. Если form_index имеет значение None, будут проверены ошибки, не связанные с формой (ошибки, доступные через formset.non_form_errors())

field - имя поля в форме для проверки. Если field имеет значение None, будут проверены ошибки, не связанные с полем (ошибки, доступные через form.non_field_errors()).

errors - строка ошибки или список строк ошибок, ожидаемых в результате валидации формы.

SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False) [source]

Утверждает, что экземпляр Response произвёл заданный status_code, и что text содержится в контенте ответа. Если count предоставлено, text должен появляться ровно count раз в ответе.

Установите html на True, чтобы обработать text как HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не существенен. Дополнительную информацию см. в assertHTMLEqual().

SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False) [source]

Утверждает, что экземпляр Response произвёл заданный status_code, и что text не содержится в контенте ответа.

Установите html на True, чтобы обработать text как HTML. Сравнение с содержимым ответа будет основано на семантике HTML, а не на точном равенстве символов. Пробелы игнорируются в большинстве случаев, порядок атрибутов не существенен. Дополнительную информацию см. в assertHTMLEqual().

SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None) [source]

Утверждает, что шаблон с заданным именем был использован при рендеринге ответа.

Имя — строка, например 'admin/index.html'.

Аргумент count — целое число, указывающее, сколько раз должен быть отрисован шаблон. По умолчанию это None, что означает, что шаблон должен быть отрисован один или более раз.

Можно использовать его как менеджер контекста, например так:

with self.assertTemplateUsed('index.html'):
    render_to_string('index.html')
with self.assertTemplateUsed(template_name='index.html'):
    render_to_string('index.html')
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='') [source]

Утверждает, что шаблон с заданным именем не использовался при рендеринге ответа.

Можно использовать его как менеджер контекста, так же, как и assertTemplateUsed().

SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='') [source]
Новое в Django 2.2.

Утверждает, что два URL-адреса одинаковы, игнорируя порядок параметров строки запроса, за исключением параметров с одинаковым именем. Например, /path/?x=1&y=2 равно /path/?y=2&x=1, но /path/?a=1&a=2 не равно /path/?a=2&a=1.

SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) [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 всегда вызывается, даже если обе строки идентичны.

Декларация и комментарии XML игнорируются. Сравниваются только корневой элемент и его дочерние элементы.

Вывод в случае ошибки может быть настроен с помощью аргумента msg.

SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) [source]

Утверждает, что строки xml1 и xml2 не равны. Сравнение основано на семантике XML. См. assertXMLEqual() для подробностей.

Вывод в случае ошибки может быть настроен с помощью аргумента msg.

SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='') [source]

Утверждает, что фрагмент HTML needle содержится в haystack.

Если указан целочисленный аргумент count, то дополнительно будет строго проверяться количество needle.

Пробелы в большинстве случаев игнорируются, а порядок атрибутов не имеет значения. Передаваемые аргументы должны быть валидным HTML.

SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) [source]

Утверждает, что фрагменты JSON raw и expected_data равны. Обычно незначащие пробелы JSON применяются, поскольку тяжёлая работа делегируется библиотеке json.

Вывод в случае ошибки может быть настроен с помощью аргумента msg.

SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None) [source]

Утверждает, что фрагменты JSON raw и expected_data не равны. См. assertJSONEqual() для получения дополнительных подробностей.

Вывод в случае ошибки может быть настроен с помощью аргумента msg.

TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True, msg=None) [source]

Утверждает, что запрошенный набор qs возвращает определённый список значений values.

Сравнение содержимого qs и values выполняется с помощью функции transform; по умолчанию это означает, что repr() каждого значения сравнивается. Любой другой вызываемый объект может быть использован, если repr() не предоставляет уникального или полезного сравнения.

По умолчанию сравнение также зависит от порядка. Если qs не предоставляет неявного порядка, вы можете установить параметр ordered в False, что превратит сравнение в сравнение по collections.Counter. Если порядок не определён (если заданный qs не упорядочен и сравнение выполняется с более чем одним упорядоченным значением), будет выброшено ValueError.

Вывод в случае ошибки может быть настроен с помощью аргумента msg.

TransactionTestCase.assertNumQueries(num, func, *args, **kwargs) [source]

Утверждает, что при вызове func с *args и **kwargs выполняется num баз данных запросов.

Если ключ "using" присутствует в kwargs он используется как псевдоним базы данных для проверки числа запросов. Если вы хотите вызвать функцию с параметром using вы можете сделать это, обернув вызов в lambda для добавления дополнительного параметра:

self.assertNumQueries(7, lambda: my_function(using=7))

Вы также можете использовать это как менеджер контекста:

with self.assertNumQueries(2):
    Person.objects.create(name="Aaron")
    Person.objects.create(name="Daniel")

Тегирование тестов

Вы можете добавлять теги к своим тестам, чтобы легко запускать определённый подмножество. Например, вы можете пометить быстрые или медленные тесты:

from django.test import tag

class SampleTestCase(TestCase):

    @tag('fast')
    def test_fast(self):
        ...

    @tag('slow')
    def test_slow(self):
        ...

    @tag('slow', 'core')
    def test_slow_but_core(self):
        ...

Вы также можете добавить тег к случаю тестирования:

@tag('slow', 'core')
class SampleTestCase(TestCase):
    ...

Подклассы наследуют теги от суперклассов, а методы наследуют теги от своего класса. Дано:

@tag('foo')
class SampleTestCaseChild(SampleTestCase):

    @tag('bar')
    def test(self):
        ...

SampleTestCaseChild.test будет помечен тегами 'slow', 'core', 'bar', и 'foo'.

Изменено в Django 2.1:

В более старых версиях помеченные тестами тесты не наследуют теги от классов, а помеченные подклассы не наследуют теги от суперклассов. Например, SampleTestCaseChild.test помечен только тегом 'bar'.

Затем вы можете выбрать, какие тесты запускать. Например, чтобы запустить только быстрые тесты:

$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast

Или чтобы запустить быстрые тесты и тесты ядра (даже если они медленные):

$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core

Вы также можете исключить тесты по тегу. Чтобы запустить тесты ядра, если они не медленные:

$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow

test --exclude-tag имеет приоритет над test --tag, поэтому если у теста есть два тега, и вы выбрали один из них, а другой исключили, тест не будет запущен.

Услуги электронной почты

Если какие-либо ваши представления Django отправляют электронные письма с помощью функциональности отправки электронных писем Django, вам, вероятно, не нужно отправлять электронные письма каждый раз, когда вы запускаете тест с использованием этого представления. По этой причине тестовый запуск Django автоматически перенаправляет все отправленные Django электронные письма в тестовый ящик выходящей почты. Это позволяет проверить все аспекты отправки электронных писем — от количества отправленных сообщений до содержимого каждого сообщения — без фактической отправки сообщений.

Тестовый запуск достигает этого, прозрачно заменяя обычный бэкенд электронной почты бэкендом для тестирования. (Не беспокойтесь — это никак не повлияет на другие отправители электронных писем за пределами Django, такие как почтовый сервер вашего компьютера, если вы его используете.)

django.core.mail.outbox

Во время выполнения теста каждое исходящее электронное письмо сохраняется в django.core.mail.outbox. Это простой список всех EmailMessage экземпляров, которые были отправлены. Атрибут outbox — это специальный атрибут, который создается только при использовании бэкенда электронной почты для тестирования. Он обычно не существует в составе модуля django.core.mail и его нельзя импортировать напрямую. Приведенный ниже код демонстрирует правильный способ доступа к этому атрибуту.

Вот пример теста, который проверяет django.core.mail.outbox по длине и содержанию:

from django.core import mail
from django.test import TestCase

class EmailTest(TestCase):
    def test_send_email(self):
        # Send message.
        mail.send_mail(
            'Subject here', 'Here is the message.',
            'from@example.com', ['to@example.com'],
            fail_silently=False,
        )

        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the subject of the first message is correct.
        self.assertEqual(mail.outbox[0].subject, 'Subject here')

Как отмечалось ранее, тестовый ящик выходящей почты очищается в начале каждого теста в Django *TestCase. Чтобы очистить ящик выходящей почты вручную, присвойте пустой список mail.outbox:

from django.core import mail

# Empty the test outbox
mail.outbox = []

Команды управления

Команды управления можно протестировать с помощью функции call_command(). Вывод можно перенаправить в экземпляр StringIO:

from io import StringIO
from django.core.management import call_command
from django.test import TestCase

class ClosepollTest(TestCase):
    def test_command_output(self):
        out = StringIO()
        call_command('closepoll', stdout=out)
        self.assertIn('Expected output', out.getvalue())

Пропуск тестов

Библиотека unittest предоставляет декораторы @skipIf и @skipUnless, позволяющие пропускать тесты, если заранее известно, что эти тесты могут потерпеть неудачу при определенных условиях.

Например, если для успеха вашего теста требуется определенная дополнительная библиотека, вы можете использовать декоратор @skipIf. В этом случае тестовый запуск сообщит о том, что тест не был выполнен и почему, вместо того, чтобы завершить тест с ошибкой или исключить тест вообще.

Для дополнения этих механизмов пропуска тестов Django предоставляет два дополнительных декоратора для пропуска. Вместо проверки общего булевого значения эти декораторы проверяют возможности базы данных и пропускают тест, если база данных не поддерживает определённую именованную функцию.

Декораторы используют строковый идентификатор для описания функций базы данных. Эта строка соответствует атрибутам класса функций подключения к базе данных. Полный список функций базы данных, которые можно использовать в качестве основы для пропуска тестов, см. в классе django.db.backends.BaseDatabaseFeatures.

skipIfDBFeature(*feature_name_strings) [source]

Пропустить декорированный тест или TestCase если все указанные функции базы данных поддерживаются.

Например, следующий тест не будет выполнен, если база данных поддерживает транзакции (например, он не будет запущен под PostgreSQL, но будет под MySQL с таблицами MyISAM):

class MyTests(TestCase):
    @skipIfDBFeature('supports_transactions')
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
skipUnlessDBFeature(*feature_name_strings) [source]

Пропустить декорированный тест или TestCase если любая из указанных функций базы данных не поддерживается.

Например, следующий тест будет выполнен только в том случае, если база данных поддерживает транзакции (например, он будет запущен под PostgreSQL, но не под MySQL с таблицами MyISAM):

class MyTests(TestCase):
    @skipUnlessDBFeature('supports_transactions')
    def test_transaction_behavior(self):
        # ... conditional test code
        pass

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.2/topics/testing/tools/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API