Spec-Zone.ru › Django 2.1

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

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

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

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

С помощью клиента для тестирования можно:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/login/

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

name=fred&passwd=secret

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

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

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

Если вы укажете любой другой content_type (например, text/xml для XML-загрузки), содержимое data будет отправлено как есть в запросе POST, используя content_type в HTTP-заголовке Content-Type.

Если вы не укажете значение для content_type, значения в data будут переданы со значением типа содержимого multipart/form-data. В этом случае ключево-значимые пары в data будут закодированы как multipart-сообщение и использованы для создания данных загрузки POST.

Чтобы отправить несколько значений для одного ключа — например, для указания выделенных значений для <select multiple>, — укажите значения в виде списка или кортежа для необходимого ключа. Например, это значение data отправит три выбранных значения для поля с именем choices.

{'choices': ('a', 'b', 'd')}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

login(**credentials) [source]

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

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

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

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

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

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

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

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

force_login(user, backend=None) [source]

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

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

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

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

logout() [source]

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

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

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

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

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

class Response
client

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

content

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

context

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

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

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

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

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

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

json(**kwargs)

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

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

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

request

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

wsgi_request

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

status_code

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

templates

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

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

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

resolver_match

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

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

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

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

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

Исключения

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

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

Состояние

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

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

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

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 предоставляет несколько расширений этого базового класса:

Иерархия классов тестирования Django (подклассы TestCase)

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

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

SimpleTestCase

class SimpleTestCase [source]

Подкласс unittest.TestCase, который добавляет эту функциональность:

  • Некоторые полезные утверждения, такие как:
    • Проверка того, что вызываемый объект raises a certain exception.
    • Проверка того, что вызываемый объект triggers a certain warning.
    • Тестирование поля формы rendering and error treatment.
    • Тестирование HTML responses for the presence/lack of a given fragment.
    • Проверка того, что шаблон has/hasn't been used to generate a given response content.
    • Проверка выполнения HTTP redirect приложением.
    • Надежное тестирование двух HTML fragments на равенство/неравенство или containment.
    • Надежное тестирование двух XML fragments на равенство/неравенство.
    • Надежное тестирование двух JSON fragments на равенство.
  • Возможность запуска тестов с изменёнными настройками.
  • Использование 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 базы данных.
  • Пропуск тестов в зависимости от возможностей бэкенда базы данных.
  • Специализированные методы assert*.

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

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

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

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

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

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

TestCase

class TestCase [source]

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

Класс:

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

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

classmethod TestCase.setUpTestData() [source]

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

Например:

from django.test import TestCase

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

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

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

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

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

LiveServerTestCase

class LiveServerTestCase [source]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

Функциональные возможности тестовых случаев

Стандартный тестовый клиент

SimpleTestCase.client

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

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

import unittest
from django.test import Client

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

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

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

from django.test import TestCase

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

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

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

SimpleTestCase.client_class

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

from django.test import Client, TestCase

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

class MyTest(TestCase):
    client_class = MyTestClient

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

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

TransactionTestCase.fixtures

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

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

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

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

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

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

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

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

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

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

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

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

Настройка URLconf

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

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

TransactionTestCase.multi_db

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

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

Например:

class TestMyViews(TestCase):
    multi_db = True

    def test_index_page_view(self):
        call_some_test_code()

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

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

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

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

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

SimpleTestCase.settings() [source]

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

from django.test import TestCase

class LoginTestCase(TestCase):

    def test_login(self):

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

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

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

SimpleTestCase.modify_settings() [source]

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

from django.test import TestCase

class MiddlewareTestCase(TestCase):

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

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

override_settings() [source]

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

from django.test import TestCase, override_settings

class LoginTestCase(TestCase):

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

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

from django.test import TestCase, override_settings

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

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

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

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

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

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

from django.test import TestCase, modify_settings

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

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

Примечание

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

Учитывая Python 3.5

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

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

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

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

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

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

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

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

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

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

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

Очистка тестовой папки выходящих сообщений

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

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

Ассерции

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Проверяет, что строки html1 и html2 не равны. Сравнение основано на семантике HTML. См. assertHTMLEqual() для получения дополнительной информации.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Утверждает, что JSON-фрагменты raw и expected_data не равны. Дополнительные детали см. в assertJSONEqual().

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.test import tag

class SampleTestCase(TestCase):

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

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

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

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

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

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

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

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

'slow', 'core', 'bar', и 'foo'.

Изменено в 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.1/topics/testing/tools/

Spec-Zone.ru

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