Spec-Zone.ru › Django 5.1

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

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

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

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

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

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

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

  • Используйте клиент для тестирования Django, чтобы убедиться, что отображается правильный шаблон и что шаблон получает правильные данные контекста.
  • Используйте RequestFactory, чтобы напрямую тестировать функции представлений, минуя маршрутизацию и уровни миддлверов.
  • Используйте фреймворки «в браузере», такие как 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, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults) [source]

Тестовый HTTP-клиент. Принимает несколько аргументов, которые могут настроить поведение.

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

client = Client(headers={"user-agent": "curl/7.79.1"})

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

Произвольные ключевые аргументы в **defaults устанавливают WSGI переменные окружения. Например, чтобы установить имя сценария:

client = Client(SCRIPT_NAME="/app/")

Примечание

Ключевые аргументы, начинающиеся с HTTP_ префикса, устанавливаются как заголовки, но параметр headers предпочтительнее для читаемости.

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

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

Аргумент raise_request_exception позволяет управлять тем, будут ли исключения, возникающие во время запроса, также возникать в тесте. По умолчанию это True.

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

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

Был добавлен аргумент query_params.

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

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

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

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

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

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

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

Также возможно передать эти параметры в параметр data. Однако query_params предпочтительнее, так как он работает для любого HTTP-метода.

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

>>> c = Client()
>>> c.get(
...     "/customers/details/",
...     query_params={"name": "fred", "age": 7},
...     headers={"accept": "application/json"},
... )

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

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

>>> c = Client()
>>> c.get("/", SCRIPT_NAME="/app/")

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

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

Если вы предоставите URL с закодированными данными GET и аргументом query_params или 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-запрос.

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

Был добавлен аргумент query_params.

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **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().

Если вы укажете любой другой 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"]}

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

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

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

>>> from io import BytesIO
>>> img = BytesIO(
...     b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00"
...     b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00"
... )
>>> img.name = "myimage.gif"

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

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

Параметры headers, query_params, и extra работают так же, как и для Client.get().

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

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

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

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

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

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

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

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

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

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

Был добавлен параметр query_params.

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

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

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

Параметры follow, secure, headers, query_params, и extra работают так же, как для Client.get().

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

Был добавлен параметр query_params.

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

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

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

Параметры follow, secure, headers, query_params, и extra работают так же, как для Client.get().

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

Был добавлен параметр query_params.

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

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

Параметры follow, secure, headers, query_params, и extra работают так же, как для Client.get().

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

Был добавлен параметр query_params.

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

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

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

Параметры follow, secure, headers, query_params, и extra работают так же, как для Client.get().

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

Был добавлен параметр query_params.

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

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

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

Параметры follow, secure, headers, query_params, и extra работают так же, как для Client.get().

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

Был добавлен параметр query_params.

login(**credentials)
alogin(**credentials)

Асинхронная версия: alogin()

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

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

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

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

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

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

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

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

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

alogin() метод был добавлен.

force_login(user, backend=None)
aforce_login(user, backend=None)

Асинхронная версия: aforce_login()

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

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

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

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

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

aforce_login() метод был добавлен.

logout()
alogout()

Асинхронная версия: alogout()

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

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

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

alogout() метод был добавлен.

Проверка ответов

Методы 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 может быть подходящей альтернативой в ответах с этим свойством.

exc_info

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

Значениями являются (тип, значение, стек отладки), аналогично тому, что возвращает функция Python sys.exc_info(). Их значения следующие:

  • тип: Тип исключения.
  • значение: Экземпляр исключения.
  • стек отладки: Объект стека отладки, который содержит стек вызовов в точке, где произошло исключение.

Если исключение не произошло, то exc_info будет None.

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 compare the view_class, as the
# functions generated by as_view() won't be equal.
self.assertIs(response.resolver_match.func.view_class, MyView)

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

Как и в обычном ответе, вы можете получить доступ к заголовкам через HttpResponse.headers. Например, определить тип содержимого ответа можно с помощью response.headers['Content-Type'].

Исключения

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

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

Если Client.raise_request_exception равно False, тестовый клиент вернёт ответ 500, как и вернётся браузеру. У ответа есть атрибут exc_info, предоставляющий информацию об обработанном исключении.

Сохраняемое состояние

Тестовый клиент является состоятельным. Если ответ возвращает 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()
Client.asession()
Добавлена в Django 5.0.

Аналогично атрибуту session, но работает в асинхронных контекстах.

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

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

Если модуль включен, язык можно установить, создав 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("/", headers={"accept-language": "fr"})
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

Примечание

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

def tearDown(self):
    translation.activate(settings.LANGUAGE_CODE)

Дополнительные сведения находятся в Как 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

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

Обычные классы модульных тестов наследуются от базового класса 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 используется.
    • Проверка того, что два URLs равны.
    • Проверка того, что приложение выполняет переадресацию HTTP redirect.
    • Надежное тестирование двух HTML fragments на равенство/неравенство или containment.
    • Надежное тестирование двух XML fragments на равенство/неравенство.
    • Надежное тестирование двух JSON fragments на равенство.
  • Возможность запускать тесты с изменёнными настройками.
  • Использование client Client.

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

SimpleTestCase.databases

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

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

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

class MyTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        ...

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

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

TransactionTestCase

class TransactionTestCase [source]

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

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

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

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

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

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

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

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

TestCase

class TestCase [source]

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

Класс:

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

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

classmethod TestCase.setUpTestData() [source]

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

Например:

from django.test import TestCase


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

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

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

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

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

classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False) [source]

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

using - это псевдоним соединения с базой данных для захвата обратных вызовов.

Если execute равно True, все обратные вызовы будут вызваны по выходу из менеджера контекста, если не произошла ошибка. Это эмулирует коммит после обработанного блока кода.

Например:

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


class ContactTests(TestCase):
    def test_post(self):
        with self.captureOnCommitCallbacks(execute=True) as callbacks:
            response = self.client.post(
                "/contact/",
                {"message": "I like your site"},
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(callbacks), 1)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, "Contact Form")
        self.assertEqual(mail.outbox[0].body, "I like your site")

LiveServerTestCase

class LiveServerTestCase [source]

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

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

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

$ python -m pip install "selenium >= 4.8.0"
...\> py -m pip install "selenium >= 4.8.0"

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

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

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
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(f"{self.live_server_url}/login/")
        username_input = self.selenium.find_element(By.NAME, "username")
        username_input.send_keys("myuser")
        password_input = self.selenium.find_element(By.NAME, "password")
        password_input.send_keys("secret")
        self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()

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

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

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

Примечание

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

def test_login(self):
    from selenium.webdriver.support.wait import WebDriverWait

    timeout = 2
    ...
    self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
    # Wait until the response is received
    WebDriverWait(self.selenium, timeout).until(
        lambda driver: driver.find_element(By.TAG_NAME, "body")
    )

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

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

По умолчанию тестовый клиент

SimpleTestCase.client

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

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

import unittest
from django.test import Client


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

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

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

from django.test import TestCase


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

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

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

SimpleTestCase.client_class

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

from django.test import Client, TestCase


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


class MyTest(TestCase):
    client_class = MyTestClient

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

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

TransactionTestCase.fixtures

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

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

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

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

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


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

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

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

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

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

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

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

Настройка URLconf

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

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

TransactionTestCase.databases

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

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

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

Например:

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

    def test_index_page_view(self):
        call_some_test_code()

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

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

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

TestCase.databases

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

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

Например:

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

    def test_other_db_query(self): ...

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

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

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

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

SimpleTestCase.settings() [source]

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

from django.test import TestCase


class LoginTestCase(TestCase):
    def test_login(self):
        # First check for the default behavior
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/accounts/login/?next=/sekrit/")

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

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

SimpleTestCase.modify_settings() [source]

Изменение настроек, содержащих список значений, может оказаться громоздким. На практике часто достаточно добавить или удалить значения. Django предоставляет менеджер контекста 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(**kwargs) [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(*args, **kwargs) [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 Шаблоны
FORM_RENDERER По умолчанию рендерер
SERIALIZATION_MODULES Кэш сериализаторов
LOCALE_PATHS, LANGUAGE_CODE По умолчанию перевод и загруженные переводы
STATIC_ROOT, STATIC_URL, STORAGES Настройка хранилищ
Изменено в Django 5.1:

Добавлена возможность сброса рендерера по умолчанию при изменении настройки FORM_RENDERER.

Изоляция приложений

utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)

Регистрирует модели, определённые в обернутом контексте, в собственном изолированном apps реестре. Эта функциональность полезна при создании моделей для тестов, так как классы будут корректно удалены после этого, и не будет рисков конфликтов имён.

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

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class MyModelTests(SimpleTestCase):
    @isolate_apps("app_label")
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        ...

… или:

with isolate_apps("app_label"):

    class TestModel(models.Model):
        pass

    ...

Форма декоратора также может быть применена к классам.

Можно указать два необязательных ключевых аргумента:

  • attr_name: атрибут, которому присваивается изолированный реестр, если используется как декоратор класса.
  • kwarg_name: ключевой аргумент, передающий изолированный реестр, если используется как декоратор функции.

Временный экземпляр Apps , используемый для изоляции регистрации моделей, может быть получен как атрибут при использовании в качестве декоратора класса, используя параметр attr_name:

@isolate_apps("app_label", attr_name="apps")
class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)

… или альтернативно в качестве аргумента в методе теста при использовании как декоратора метода, используя параметр kwarg_name:

class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label", kwarg_name="apps")
    def test_model_definition(self, apps):
        class TestModel(models.Model):
            pass

        self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)

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

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

Для более подробной информации об email-сервисах во время тестов, см. раздел Email-сервисы ниже.

Ассершены

Поскольку стандартный класс 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)

Аналогично 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(form, field, errors, msg_prefix='') [source]

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

form — это экземпляр Form. Форма должна быть связанной, но не обязательно валидированной (assertFormError() автоматически вызовет full_clean() для формы).

field — имя поля на форме для проверки. Чтобы проверить ошибки формы, не связанные с полями (non-field errors), используйте field=None.

errors — список всех ожидаемых сообщений об ошибках поля. Также можно передать единственное сообщение об ошибке, если ожидается только одна ошибка, что означает, что errors='error message' эквивалентно errors=['error message'].

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

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

formset — это экземпляр FormSet. Набор форм должен быть связанным, но не обязательно валидированным (assertFormSetError() автоматически вызовет full_clean() для набора форм).

form_index — номер формы в наборе FormSet (начиная с 0). Для проверки ошибок набора форм, не относящихся к формам, т. е. ошибок, полученных при вызове formset.non_form_errors(), используйте form_index=None. В этом случае необходимо также использовать field=None.

field и errors имеют такое же значение, как параметры assertFormError().

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

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

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

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

В более старых версиях сообщения об ошибках не содержали содержимого ответа.

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

Утверждает, что response вернул указанный status_code и что text не содержится в его content.

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

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

В более старых версиях сообщения об ошибках не содержали содержимое ответа.

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

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

response должен быть экземпляром ответа, возвращенным методом test client.

template_name должен быть строкой, например 'admin/index.html'.

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

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

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

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

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

SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='') [source]

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

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

Утверждает, что response вернул код переадресации 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-элемента не имеет значения.
  • Булевы атрибуты (например, checked) без аргумента равны атрибутам, совпадающим по имени и значению (см. примеры).
  • Текст, ссылки на символы и ссылки на сущности, которые ссылаются на тот же символ, эквивалентны.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В более старых версиях сообщения об ошибках не содержали haystack.

SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='') [source]
Новое в Django 5.1.

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

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

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=None, ordered=True, msg=None) [source]

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

Если transform предоставлено, values сравнивается со списком, полученным путём применения transform к каждому элементу qs.

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

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

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

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

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

self.assertNumQueries(7, using="non_default_db")

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

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

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

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

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

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

from django.test import tag


class SampleTestCase(TestCase):
    @tag("fast")
    def test_fast(self): ...

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

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

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

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

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

@tag("foo")
class SampleTestCaseChild(SampleTestCase):
    @tag("bar")
    def test(self): ...

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

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

$ ./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, вам нужно учесть несколько моментов.

Во-первых, ваши тесты должны быть async def методами в тестовом классе (чтобы предоставить им асинхронный контекст). Django автоматически обнаружит все async def тесты и обернёт их, чтобы они выполнялись в собственном цикле событий.

Если вы тестируете из асинхронной функции, вы также должны использовать асинхронный клиент тестов. Он доступен как django.test.AsyncClient, или как self.async_client в любом тесте.

class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults) [source]
END_OF_DOCUMENT_MARKER

AsyncClient имеет те же методы и сигнатуры, что и синхронный (обычный) клиент тестирования, за исключением следующих моментов:

  • При инициализации произвольные ключевые аргументы в defaults добавляются непосредственно в область ASGI.
  • Заголовки, передаваемые в качестве extra ключевых аргументов, не должны содержать префикс HTTP_, требуемый синхронным клиентом (см. Client.get()). Например, вот как установить HTTP-заголовок Accept:

    >>> c = AsyncClient()
    >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
    
Изменено в Django 5.0:

Поддержка параметра follow была добавлена к AsyncClient.

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

Был добавлен аргумент query_params.

Используя AsyncClient любой метод, выполняющий запрос, должен быть ожидаемым:

async def test_my_thing(self):
    response = await self.async_client.get("/some-url/")
    self.assertEqual(response.status_code, 200)

Асинхронный клиент также может вызывать синхронные представления; он выполняется через асинхронный путь обработки запросов Django, который поддерживает оба типа. Любое представление, вызываемое через AsyncClient получит объект ASGIRequest для своего request, а не WSGIRequest, создаваемый обычным клиентом.

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

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

Если вам нужно использовать эти декораторы, то вы должны декорировать ваши тестовые методы с помощью async_to_sync() внутри них:

from asgiref.sync import async_to_sync
from django.test import TestCase


class MyTests(TestCase):
    @mock.patch(...)
    @async_to_sync
    async def test_my_thing(self): ...

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

Если ваши представления 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", poll_ids=[1], stdout=out)
        self.assertIn('Successfully closed poll "1"', out.getvalue())

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

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

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

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

Декораторы используют строковый идентификатор для описания функций базы данных. Эта строка соответствует атрибутам класса функций подключения к базе данных. Обратитесь к классу django.db.backends.base.features.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/5.1/topics/testing/tools/

Spec-Zone.ru

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