Spec-Zone.ru › Django 2.0

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

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'})

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

login(**credentials) [source]

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

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

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

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

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

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

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

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

force_login(user, backend=None) [source]

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

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

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

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

logout() [source]

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

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

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

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

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

class Response
client

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

content

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

context

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

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

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

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

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

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

json(**kwargs)

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

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

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

request

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

wsgi_request

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

status_code

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

templates

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

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

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

resolver_match

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

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

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

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

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

Исключения

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

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

Состояние

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

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

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

Client.cookies

Объект Python SimpleCookie, содержащий текущие значения всех cookies клиента. Смотрите документацию модуля http.cookies для получения дополнительной информации.

Client.session

Объект, подобный словарю, содержащий информацию о сессии. Смотрите документацию по сессиям для получения полной информации.

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

def test_something(self):
    session = self.client.session
    session['somekey'] = 'test'
    session.save()

Установка языка

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

Если middleware включен, язык можно установить, создав cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:

from django.conf import settings

def test_language_using_cookie(self):
    self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
    response = self.client.get('/')
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

или включив заголовок Accept-Language HTTP в запросе:

def test_language_using_header(self):
    response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

Дополнительные сведения см. в Как Django определяет предпочтение языка.

Если middleware не включен, активный язык может быть установлен с помощью translation.override():

from django.utils import translation

def test_language_using_override(self):
    with translation.override('fr'):
        response = self.client.get('/')
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

Дополнительные сведения см. в Явное задание активного языка.

Пример

Ниже приведен простой модульный тест с использованием тестового клиента:

import unittest
from django.test import Client

class SimpleTest(unittest.TestCase):
    def setUp(self):
        # Every test needs a client.
        self.client = Client()

    def test_details(self):
        # Issue a GET request.
        response = self.client.get('/customer/details/')

        # Check that the response is 200 OK.
        self.assertEqual(response.status_code, 200)

        # Check that the rendered context contains 5 customers.
        self.assertEqual(len(response.context['customers']), 5)

См. также

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.
    • Тестирование поля формы 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.
  • Возможность запуска тестов с изменёнными настройками.
  • Использование client Client.

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

SimpleTestCase.allow_database_queries

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

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

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

class MyTestCase(TestCase):

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

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

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

TransactionTestCase

class TransactionTestCase [source]

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

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

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

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

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

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

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

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

TestCase

class TestCase [source]

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

Класс:

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

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

classmethod TestCase.setUpTestData() [source]

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

Например:

from django.test import TestCase

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

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

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

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

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

LiveServerTestCase

class LiveServerTestCase [source]

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

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

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

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

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

$ pip install selenium

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

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

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

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

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

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

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

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

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

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

Примечание

При использовании встроенной базы данных SQLite для выполнения тестов одно и то же соединение с базой данных будет использоваться двумя потоками параллельно: потоком, в котором выполняется живой сервер, и потоком, в котором выполняется тестовый случай. Важно предотвратить одновременные запросы к базе данных через это общее соединение двумя потоками, так как это может иногда случайно привести к сбою тестов. Поэтому необходимо убедиться, что оба потока не обращаются к базе данных одновременно. В частности, это означает, что в некоторых случаях (например, сразу после нажатия ссылки или отправки формы) вам может потребоваться проверить, получен ли ответ от Selenium и загружена ли следующая страница, прежде чем продолжать дальнейшее выполнение теста. Сделайте это, например, заставив Selenium ждать, пока тег <body> не будет найден в ответе (требуется Selenium > 2.13):

def test_login(self):
    from selenium.webdriver.support.wait import WebDriverWait
    timeout = 2
    ...
    self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
    # Wait until the response is received
    WebDriverWait(self.selenium, timeout).until(
        lambda driver: driver.find_element_by_tag_name('body'))

Трудность заключается в том, что в современных веб-приложениях, которые генерируют HTML динамически после того, как сервер сгенерирует исходный документ, нет такого понятия, как «загрузка страницы». Поэтому проверка наличия <body> в ответе может быть не всегда уместна для всех случаев использования. Для получения дополнительной информации см. часто задаваемые вопросы Selenium и документацию Selenium.

Особенности тестовых случаев

Клиент по умолчанию для тестирования

SimpleTestCase.client

Каждый тестовый случай в экземпляре django.test.*TestCase имеет доступ к экземпляру тестового клиента Django. К этому клиенту можно обратиться как к self.client. Этот клиент пересоздаётся для каждого теста, поэтому вам не нужно беспокоиться о сохранении состояния (например, файлов cookie) от одного теста к другому.

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

import unittest
from django.test import Client

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

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

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

from django.test import TestCase

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

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

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

SimpleTestCase.client_class

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

from django.test import Client, TestCase

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

class MyTest(TestCase):
    client_class = MyTestClient

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

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

TransactionTestCase.fixtures

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

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

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

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

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

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

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

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

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

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

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

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

Настройка URLconf

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

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

TransactionTestCase.multi_db

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

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

Например:

class TestMyViews(TestCase):
    multi_db = True

    def test_index_page_view(self):
        call_some_test_code()

Этот тестовый случай очистит все тестовые базы данных перед запуском test_index_page_view.

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

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

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

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

SimpleTestCase.settings() [source]

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

from django.test import TestCase

class LoginTestCase(TestCase):

    def test_login(self):

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

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

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

SimpleTestCase.modify_settings() [source]

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

from django.test import TestCase

class MiddlewareTestCase(TestCase):

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

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

override_settings() [source]

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

from django.test import TestCase, override_settings

class LoginTestCase(TestCase):

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

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

from django.test import TestCase, override_settings

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

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

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

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

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

Декоратор также может быть применён к классам тестов:

from django.test import TestCase, modify_settings

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

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

Ассерты

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

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

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

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

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

with self.assertRaisesMessage(ValueError, 'invalid literal for int()'):
    int('a')
SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='') [source]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Имя — это строка, такая как 'admin/index.html'.

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

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

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

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

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

SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) [source]

Утверждает, что ответ вернул переадресацию статуса status_code, перенаправил на expected_url (включая любые данные GET) и что конечная страница была получена со статусом target_status_code.

Если ваш запрос использовал аргумент follow, то expected_url и target_status_code будут URL и кодом состояния для конечной точки цепочки переадресации.

Если fetch_redirect_response равно False, конечная страница не будет загружена. Поскольку тестовый клиент не может извлекать внешние URL-адреса, это особенно полезно, если expected_url не является частью вашего приложения Django.

Схема обрабатывается корректно при сравнении двух URL-адресов. Если схема не указана в месте, куда мы перенаправлены, используется схема исходного запроса. Если указана, схема в expected_url используется для сравнения.

SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) [source]

Утверждает, что строки html1 и html2 равны. Сравнение основано на HTML-семантике. Сравнение учитывает следующие моменты:

  • Пробелы перед и после тегов HTML игнорируются.
  • Все типы пробелов считаются эквивалентными.
  • Все открытые теги неявно закрываются, например, при закрытии окружающего тега или окончании HTML-документа.
  • Пустые теги эквивалентны своим самозакрывающимся версиям.
  • Порядок атрибутов HTML-элемента не имеет значения.
  • Атрибуты без аргумента эквивалентны атрибутам, которые равны по имени и значению (см. примеры).

Следующие примеры являются допустимыми тестами и не вызывают никаких AssertionError:

self.assertHTMLEqual(
    '<p>Hello <b>world!</p>',
    '''<p>
        Hello   <b>world! <b/>
    </p>'''
)
self.assertHTMLEqual(
    '<input type="checkbox" checked="checked" id="id_accept_terms" />',
    '<input id="id_accept_terms" type="checkbox" checked>'
)

html1 и html2 должны быть валидным HTML. Ошибка AssertionError будет поднята, если один из них не может быть проанализирован.

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

SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) [source]

Утверждает, что строки html1 и html2 не равны. Сравнение основано на HTML-семантике. См. assertHTMLEqual() для получения подробностей.

html1 и html2 должны быть валидным HTML. Ошибка AssertionError будет поднята, если один из них не может быть проанализирован.

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

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

Утверждает, что строки xml1 и xml2 равны. Сравнение основано на XML-семантике. Аналогично assertHTMLEqual(), сравнение выполняется на обработанном содержимом, поэтому учитываются только семантические, а не синтаксические различия. При передаче некорректного XML в любой параметр, ошибка AssertionError всегда генерируется, даже если обе строки идентичны.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.test import tag

class SampleTestCase(TestCase):

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

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

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

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

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

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

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

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

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

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

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

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

Сервисы электронной почты

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

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

django.core.mail.outbox

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

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

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

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

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

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

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

from django.core import mail

# Empty the test outbox
mail.outbox = []

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

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

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

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

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

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

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

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

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

skipIfDBFeature(*feature_name_strings) [source]

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

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

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

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

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

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

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

Spec-Zone.ru

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