• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

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

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

Тестовый клиент

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

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

  • Эмулирует GET или POST запросы к URL-у и обрабатывает ответ – начиная от низкоуровневого HTTP (заголовки результата и код ответа) и заканчивая содержимым ответа.

  • Следует по цепочке редиректов (если такие есть) и проверяет URL и код ответа на каждом шаге.

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

Обратите внимание, этот клиент не может заменить Selenium или другие фреймворки, которые используют движок браузера для запросов. У тестового клиента 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)

Тестируемый 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().

Создав экземпляр Client, вы можете использовать следующие методы:

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

Выполняет 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 в форме URL-адреса, вы можете использовать эту кодировку вместо использования аргумента данных. Например, предыдущий запрос 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 запрос.

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)

Выполняет 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, данные сериализуются с использованием json.dumps(), если это словарь, список или кортеж. Сериализация выполняется с помощью DjangoJSONEncoder по умолчанию и может быть переопределена, предоставив аргумент json_encoder для Client. Эта сериализация также происходит для запросов put(), patch() и delete().

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

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

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

{"choices": ["a", "b", "d"]}

Отправка файлов — это особый случай. Чтобы отправить файл POST, вам нужно только указать имя поля файла в качестве ключа и дескриптор файла, который вы хотите загрузить, в качестве значения. Например, если в вашей форме есть поля 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 запрос.

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

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

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

Выполняет OPTIONS запрос по указанному path и возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.

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

Параметры follow, secure, headers, query_params и extra действуют так же, как и для Client.get`().

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

Выполняет PUT запрос по указанному path и возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.

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

Параметры follow, secure, headers, query_params и extra действуют так же, как и для Client.get`().

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

Выполняет PATCH запрос по указанному path и возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.

Параметры follow, secure, headers, query_params и extra действуют так же, как и для Client.get`().

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

Выполняет DELETE запрос по указанному path и возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.

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

Параметры follow, secure, headers, query_params и extra действуют так же, как и для Client.get`().

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

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

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

Параметры follow, secure, headers, query_params и extra действуют так же, как и для Client.get`().

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

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

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

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

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

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

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

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

login() возвращает True, если пользователь успешно залогинен.

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

force_login(user, backend=None)
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(), используя более простые алгоритмы хэширования паролей.

logout()
alogout()

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

Если ваш сайт использует систему авторизации 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.

exc_info

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

Значения (тип, значение, обратная трассировка) такие же, как и возвращаемые Python sys.exc_info(). Их значения таковы:

  • тип: тип исключения.

  • значение: Экземпляр исключения.

  • traceback: объект трассировки, который инкапсулирует стек вызовов в той точке, где изначально произошло исключение.

Если никакого исключения не произошло, то 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 статус ответа, число. Полный список возможных HTTP статусов можно найти в спецификации RFC 2616 Section 10.

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'].

Исключения

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

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

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

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

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

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

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

Client.cookies

Объект SimpleCookie, который содержит текущие куки клиента. Подробности смотрите в документации модуля http.cookies.

Client.session

Объект с API словаря, который содержит данные сессии. Подробности смотрите в в разделе о сессии.

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

def test_something(self):
    session = self.client.session
    session["somekey"] = "test"
    session.save()
Client.asession()

Это похоже на атрибут 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.")

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

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 определяет языковую настройку.

Если промежуточное программное обеспечение не включено, активный язык можно установить с помощью 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 в Django TestCase очень просто: замените базовый класс теста с 'unittest.TestCase' на 'django.test.TestCase'. Все стандартные возможности тестов в Python будут доступны, но в дополнение к ним при запуске теста:

SimpleTestCase

class SimpleTestCase

Наследуется от unittest.TestCase предоставляет некоторые дополнительные возможности:

используйте TransactionTestCase или TestCase.

SimpleTestCase.databases

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

TransactionTestCase наследуется от SimpleTestCase.

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

TransactionTestCase и TestCase отличаются лишь механизмом сброса базы данных перед каждым тестом и мозможностью тестировать транзакции:

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

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

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

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

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

TestCase

class TestCase

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

Класс:

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

  • Проверяет отложенные ограничения базы данных в конце каждого теста.

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

classmethod TestCase.setUpTestData()

Описанный выше 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)

Возвращает менеджер контекста, который перехватывает обратные вызовы 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

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

По умолчанию сервер слушает localhost, выбирая первый свободный порт из 8081-8179. Полный URL к серверу можно получить в тестах через атрибут self.live_server_url.

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

$ python -m pip install "selenium >= 4.23.0"
...\> py -m pip install "selenium >= 4.23.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, затем страницу авторизации, вводит логин и пароль и нажимает кнопку «Log in». Selenium поддерживает и другие драйверы, если у вас не установлен Firefox, или вы хотите использовать другие браузеры. Пример выше показывает лишь малую часть возможностей Selenium; подробности смотрите в документации.

Примечание

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

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

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

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

Возможности приложения для тестирования

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

SimpleTestCase.client

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

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

import unittest
from django.test import Client


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

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

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

from django.test import TestCase


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

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

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

SimpleTestCase.client_class

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

from django.test import 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(), однако вы также можете использовать fixtures.

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

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

После создания фикстур, добавьте их в каталог 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()

Что произойдет:

  • Во время setUpClass() устанавливаются все названные приборы. В этом примере Django установит любое приспособление JSON с именем «млекопитающие», а затем любое приспособление с именем «птицы». См. тему Фикстуры для получения более подробной информации об определении и установке фикстур.

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

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

  • Для каждого последующего теста фикстуры будут перезагружаться перед запуском setUp().

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

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

Changed in Django 5.2:

Для TransactionTestCase фикстуры стали доступны во время setUpClass().

Конфигурация URLconf

Если выше приложение содержит представления, вам понадобятся тесты, которые вызывают их, используя тестовый клиент. Однако, пользователь приложения может добавить представления на любой URL. Это означает, что тесты не могут полагаться на то, что представление доступно по определенному URL-у.

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

TransactionTestCase.databases

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

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

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

Например:

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

    def test_index_page_view(self):
        call_some_test_code()

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

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

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

TestCase.databases

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

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

Например:

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

    def test_other_db_query(self): ...

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

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

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

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

SimpleTestCase.settings()

При тестировании часто необходимо временно переопределить настройки и вернуть начальные значение после завершения тестов. Для таких случаев 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()

Помогает при переопределении настроек, которые содержат список. Если вам необходимо добавить или удалить значение из списка, используйте контекстный менеджер 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)

Если вы хотите переопределить настройки для тестового метода, 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)

Также 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 илиr MiddlewareTestCase, вы обнаружите, что оригинальные классы были все равно изменены декораторами. Декоратор modify_settings() всегда применяет после override_settings(), если их добавить к одному классу.

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

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

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

Также не указывайте ваши настройки в константах модуля, override_settings() не будет работать с такими настройками т.к. они выполняются только при первом импорте модуля. (FIXME: whut?)

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

@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, ХРАНЕНИЯ

Конфигурация хранилищ

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

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, содержимое отправленных тестовых электронных писем будет очищено перед каждым тестом.

Подробности смотрите ниже в разделе об отправке писем.

Проверки

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

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

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

Проверяет, что при выполнении функции callable вызывается исключение expected_exception с сообщением expected_message. Похож на 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)
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)

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

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

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

Параметры:
  • 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='')

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Аргумент count представляет собой целое число, указывающее, сколько раз шаблон должен быть отображен. По умолчанию установлено значение «Нет», что означает, что шаблон должен отображаться один или несколько раз.

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

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

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

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

SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')

Утверждает, что два 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)

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

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

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

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

SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)

Сравнивает строки 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)

Проверяет, что строки html1 и html2 отличаются. Сравнение основано на семантике HTML. Подробности смотрите в описании assertHTMLEqual().

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

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

SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)

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

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

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

SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)

Проверяет, что строки xml1 и xml2 не одинаковы. При сравнении используется семантика XML. Подробности смотрите в описании assertXMLEqual().

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

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

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

Если указан параметр count, проверяется, что needle встречается указанное число раз.

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

SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='')

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

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

SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)

Проверяет, что JSON в raw и expected_data одинаковый. Обработка пробелов в JSON делегируется библиотеке json.

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

SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)

Проверяет, что фрагменты JSON raw и expected_data не одинаковы. Подробности смотрите в описании assertJSONEqual().

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

TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)

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

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

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

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

TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)

Проверяет, что при вызове функции func с аргументами *args и **kwargs выполнилось num запросов в базу данных.

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

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

Если kwargs содержит ключ "using", он используется для определения базы данных, для которой необходимо считать количество запросов. Если вы хотите вызывать функцию с аргументом 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)

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

  • При инициализации произвольные аргументы ключевого слова в defaults добавляются непосредственно в область действия ASGI.

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

    >>> c = AsyncClient()
    >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
    

При использовании AsyncClient необходимо ожидать любого метода, отправляющего запрос:

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

Асинхронный клиент также может вызывать синхронные представления; он выполняется через асинхронный путь запроса Django, который поддерживает оба варианта. Любое представление, вызванное через AsyncClient, получит объект ASGIRequest для своего запроса, а не 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 — это специальный атрибут, который создается только при использовании почтового сервера locmem. Обычно он не существует как часть модуля django.core.mail, и вы не можете импортировать его напрямую. Код ниже показывает, как правильно получить доступ к этому атрибуту.

Этот пример проверяет размер и содержимое django.core.mail.outbox:

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


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

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

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

Как упоминалось выше, содержимое этого атрибута очищается при запуске каждого теста из *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 class.

skipIfDBFeature(*feature_name_strings)

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

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

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

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

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

class MyTests(TestCase):
    @skipUnlessDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
Back to Top