Включенные инструменты тестирования


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

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

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

Моделируйте запросы GET и POST на URL и наблюдайте за ответом - все, от низкоуровневого HTTP (заголовки результатов и коды состояния) до содержимого страницы.
Посмотрите цепочку перенаправлений (если таковые имеются) и проверьте URL и код состояния на каждом этапе.
Проверьте, что заданный запрос отображается заданным шаблоном Django, с контекстом шаблона, содержащим определенные значения.
Обратите внимание, что тестовый клиент не предназначен для замены Selenium или других «внутрибраузерных» фреймворков. Тестовый клиент Django имеет другую направленность. Вкратце:

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

Обзор и небольшой пример
Чтобы использовать тестовый клиент, инстанцируйте django.test.Client и получите веб-страницы:

>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'
Как следует из этого примера, вы можете инстанцировать Client из сеанса интерактивного интерпретатора Python.

Обратите внимание на несколько важных моментов в работе тестового клиента:

Тестовый клиент не требует, чтобы веб-сервер был запущен. На самом деле, он будет прекрасно работать и без веб-сервера! Это потому, что он избегает накладных расходов HTTP и работает напрямую с фреймворком Django. Это помогает быстро запускать модульные тесты.

При получении страниц не забывайте указывать путь URL, а не весь домен. Например, правильно будет:

>>> c.get('/login/')
Это неверно:

>>> c.get('https://www.example.com/login/')
Тестовый клиент не способен получать веб-страницы, которые не работают с вашим проектом Django. Если вам нужно получить другие веб-страницы, используйте модуль стандартной библиотеки Python, такой как urllib.

Для разрешения URL-адресов тестовый клиент использует тот URLconf, на который указывает ваша настройка ROOT_URLCONF.

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

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

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

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

>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)
Выполнение запросов
Используйте класс django.test.Client для выполнения запросов.

class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults)[исходный код]
Он не требует аргументов во время построения. Однако вы можете использовать ключевые аргументы для указания некоторых заголовков по умолчанию. Например, эта программа будет отправлять HTTP-заголовок User-Agent в каждом запросе:

>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
Значения из аргументов ключевого слова extra, переданных в get(), post() и т.д., имеют приоритет над значениями по умолчанию, переданными в конструктор класса.

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

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

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

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

get(path, data=None, follow=False, secure=False, **extra)[исходный код]
Выполняет GET-запрос на предоставленный path и возвращает объект Response, который документирован ниже.

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

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
…приведет к оценке GET-запроса, эквивалентного:

/customers/details/?name=fred&age=7
Параметр аргументов с ключевым словом extra может использоваться для указания заголовков, которые должны быть отправлены в запросе. Например:

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
... HTTP_ACCEPT='application/json')
…отправит HTTP-заголовок HTTP_ACCEPT в представление деталей, что является хорошим способом тестирования путей кода, использующих метод django.http.HttpRequest.accepts().

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

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

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

>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')
Если вы предоставляете URL с закодированными данными GET и аргументом data, аргумент data будет иметь приоритет.

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

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

>>> response = c.get('/redirect_me/', follow=True)
>>> response.redirect_chain
[('http://testserver/next/', 302), ('http://testserver/final/', 302)]
Если вы установите secure в True, клиент будет эмулировать запрос HTTPS.

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)[исходный код]
Выполняет 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(), если это dict, список или кортеж. По умолчанию сериализация выполняется с помощью 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 будут закодированы как многокомпонентное сообщение и использованы для создания полезной нагрузки данных POST.

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

{'choices': ('a', 'b', 'd')}
Отправка файлов - это особый случай. Чтобы отправить файл, достаточно указать имя поля file в качестве ключа, а в качестве значения - handle файла, который вы хотите загрузить. Например:

>>> c = Client()
>>> with open('wishlist.doc', 'rb') as fp:
... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
(Имя attachment здесь не имеет значения; используйте любое имя, которое ожидает ваш код обработки файлов.)

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

>>> from io import BytesIO
>>> img = BytesIO(b'mybinarydata')
>>> img.name = 'myimage.jpg'
Обратите внимание, что если вы хотите использовать один и тот же дескриптор файла для нескольких вызовов post(), то вам нужно будет вручную сбрасывать указатель файла между вызовами. Самый простой способ сделать это - вручную закрыть файл после того, как он был предоставлен post(), как показано выше.

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

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

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

>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
… представление, обрабатывающее этот запрос, может запросить request.POST, чтобы получить имя пользователя и пароль, и может запросить request.GET, чтобы определить, был ли пользователь посетителем.

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

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

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

options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[исходный код]
Выполняет запрос OPTIONS на предоставленный path и возвращает объект Response. Используется для тестирования RESTful интерфейсов.

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

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

put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[исходный код]
Выполняет запрос PUT на предоставленный path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

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

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

patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[исходный код]
Выполняет запрос PATCH на предоставленный path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

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

delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[исходный код]
Делает запрос DELETE на предоставленный path и возвращает объект Response. Полезно для тестирования RESTful интерфейсов.

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

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

trace(path, follow=False, secure=False, **extra)[исходный код]
Делает запрос TRACE на предоставленный path и возвращает объект Response. Полезен для имитации диагностических зондов.

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

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

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

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

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

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

# Now you can access a view that's only available to logged-in users.
Если вы используете другой бэкенд аутентификации, этот метод может потребовать другие учетные данные. Он требует те учетные данные, которые требуются для метода authenticate() вашего бэкенда.

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

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

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

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

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

Этот метод быстрее, чем login(), поскольку обходятся дорогостоящие алгоритмы хэширования паролей. Кроме того, вы можете ускорить login() на using a weaker hasher while testing.

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

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

Значения (type, value, traceback), те же, что возвращает Python sys.exc_info(). Их значения следующие:

тип: Тип исключения.
значение: Экземпляр исключения.
traceback: Объект 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-статус ответа, в виде целого числа. Полный список определенных кодов см. в IANA status code registry.

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

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

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

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

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

# class-based views need to be compared by name, as the functions
# generated by as_view() won't be equal
self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__)
Если заданный URL не найден, обращение к этому атрибуту вызовет исключение Resolver404.

Как и в случае с обычным ответом, вы также можете получить доступ к заголовкам через 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, содержащий текущие значения всех клиентских cookies. Подробнее см. документацию модуля http.cookies.

Client.session
Словарно-подобный объект, содержащий информацию о сеансе. Подробную информацию см. в session documentation.

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

def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
Настройка языка
При тестировании приложений, поддерживающих интернационализацию и локализацию, вам может понадобиться установить язык для запроса тестового клиента. Метод для этого зависит от того, включен или нет параметр 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('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробную информацию можно найти в Как 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 предоставляет несколько расширений этого базового класса:

Hierarchy of Django unit testing classes (TestCase subclasses)
Иерархия классов модульного тестирования Django

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

SimpleTestCase
class SimpleTestCase[исходный код]
Подкласс 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 на равенство.
Возможность запускать тесты с modified settings.
Используя client Client.
Если ваши тесты делают какие-либо запросы к базе данных, используйте подклассы TransactionTestCase или TestCase.

SimpleTestCase.databases
SimpleTestCase запрещает запросы к базе данных по умолчанию. Это помогает избежать выполнения запросов на запись, которые повлияют на другие тесты, поскольку каждый SimpleTestCase тест не выполняется в транзакции. Если вас не беспокоит эта проблема, вы можете отключить это поведение, установив атрибут databases class в '__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[исходный код]
TransactionTestCase наследуется от SimpleTestCase, чтобы добавить некоторые специфические для базы данных возможности:

Сброс базы данных в известное состояние в начале каждого теста для облегчения тестирования и использования ORM.
База данных fixtures.
Тест skipping based on database backend features.
Остальные специализированные методы assert*.
Класс Django TestCase является более часто используемым подклассом класса TransactionTestCase, который использует средства транзакций базы данных для ускорения процесса сброса базы данных в известное состояние в начале каждого теста. Следствием этого, однако, является то, что некоторые поведения базы данных не могут быть протестированы в классе Django TestCase. Например, вы не можете проверить, что блок кода выполняется в рамках транзакции, как это требуется при использовании select_for_update(). В таких случаях следует использовать TransactionTestCase.

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

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

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

Apps will not see their data reloaded; если вам нужна эта функциональность (например, сторонние приложения должны включить ее), вы можете установить 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() будет вызываться перед каждым тестом, сводя на нет преимущества в скорости.

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

classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[исходный код]
New in Django 3.2.
Возвращает менеджер контекста, который перехватывает 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')
Changed in Django 4.0:
В старых версиях новые обратные вызовы, добавленные во время выполнения обратных вызовов transaction.on_commit(), не перехватывались.

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

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

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

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

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

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

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

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

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

def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Наконец, вы можете запустить тест следующим образом:

/ 
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
В этом примере автоматически откроется Firefox, затем перейдите на страницу входа, введите учетные данные и нажмите кнопку «Войти». Selenium предлагает другие драйверы на случай, если у вас не установлен Firefox или вы хотите использовать другой браузер. Приведенный выше пример - лишь малая часть того, что может делать клиент Selenium; для получения более подробной информации ознакомьтесь с full reference.

Примечание

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

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

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

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 class в вашем django.test.TestCase subclass:

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

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

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

def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Вот что конкретно произойдет:

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

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

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

Под