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

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класс, чтобы делать запросы.

classClient ( enforce_csrf_checks = False , json_encoder = DjangoJSONEncoder , ** по умолчанию )

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

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

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

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

Если вы установите 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( путь , данные = Нет , content_type = MULTIPART_CONTENT , follow = False , secure = False , ** дополнительно )

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

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

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

… Приведет к оценке запроса POST на этот URL:

/login/

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

name=fred&passwd=secret

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

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

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

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

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

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

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

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

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

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

options( путь , данные = '' , 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( путь , данные = '' , 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( путь , данные = '' , content_type = 'application / octet-stream' , follow = False , secure = False , ** extra )

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

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

delete( путь , данные = '' , 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( путь , следовать = False , secure = False , ** дополнительно )

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

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

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

login( ** учетные данные )

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

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

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

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

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

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

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

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

force_login( пользователь , backend = None )

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

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

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

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

logout()

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

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

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

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

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

класс 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(). Их значения:

  • type : тип исключения.
  • значение : экземпляр исключения.
  • 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 .

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 ... exceptassertRaises()

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

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

Постоянное состояние

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

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

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

Client.cookies

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

Client.session

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

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

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

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

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

Если промежуточное ПО включено, язык можно установить, создав файл cookie с именем LANGUAGE_COOKIE_NAMEи значением кода языка:

from django.conf import settings

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

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

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

Подробнее читайте в Как Django обнаруживает языковые предпочтения .

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

from django.utils import translation

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

Подробнее см. В разделе «Явная установка активного языка» .

Пример

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

import unittest
from django.test import Client

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

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

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

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

Смотрите также

django.test.RequestFactory

Предоставляемые классы тестовых примеров

Обычные классы модульных тестов Python расширяют базовый класс unittest.TestCase. Django предоставляет несколько расширений этого базового класса:

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

Иерархия классов модульного тестирования Django

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

SimpleTestCase

класс SimpleTestCase

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

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

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

debug()Метод был реализован , чтобы запустить тест без сбора результата и отлов исключения.

TransactionTestCase

класс TransactionTestCase

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

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

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

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

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

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

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

TestCase

класс TestCase

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

Класс:

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

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

classmethodTestCase.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()они будут вызываться перед каждым тестом, что отрицательно сказывается на скорости.

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

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

classmethodTestCase.captureOnCommitCallbacks ( using = DEFAULT_DB_ALIAS , execute = False )
Новое в 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')

LiveServerTestCase

класс LiveServerTestCase

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

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

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

$ python -m pip install selenium
... \> py -m pip установить селен

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

SimpleTestCase.client

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

Это означает, что вместо создания экземпляра a 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атрибут 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 dumpdatadumpdata documentation

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

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

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

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

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

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

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

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

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

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

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

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

TransactionTestCase.databases

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

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

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

Например:

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

    def test_index_page_view(self):
        call_some_test_code()

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

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

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

TestCase.databases

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

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

Например:

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

    def test_other_db_query(self):
        ...

Этот тест разрешит запросы только к otherбазе данных. Как и для 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()

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

from django.test import TestCase

class MiddlewareTestCase(TestCase):

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

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

override_settings()

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

Точно так же Django предоставляет modify_settings() декоратор:

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

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

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

from django.test import TestCase, modify_settings

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

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

Примечание

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

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

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

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

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

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

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

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

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

Переопределенные настройки Сброс данных
USE_TZ, TIME_ZONE Часовой пояс баз данных
ШАБЛОНЫ Шаблонные движки
SERIALIZATION_MODULES Кеш сериализаторов
LOCALE_PATHS, LANGUAGE_CODE Перевод по умолчанию и загруженные переводы
MEDIA_ROOT, DEFAULT_FILE_STORAGE Хранилище файлов по умолчанию

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

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

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

Утверждения

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

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

SimpleTestCase.assertRaisesMessage( Expected_exception , expected_message , вызываемые , * Args , ** kwargs )
SimpleTestCase.assertRaisesMessage( ожидаемое_исключение , ожидаемое_сообщение )

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

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

with self.assertRaisesMessage(ValueError, 'invalid literal for int()'):
    int('a')
SimpleTestCase.assertWarnsMessage( ожидаемое_предупреждение , ожидаемое_сообщение , вызываемое , * аргументы , ** kwargs )
SimpleTestCase.assertWarnsMessage( ожидаемое_предупреждение , ожидаемое_сообщение )

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

SimpleTestCase.assertFieldOutput( fieldclass , действительный , недопустимый , field_args = None , field_kwargs = None , empty_value = '' )

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

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

Например, следующий код проверяет, EmailFieldпринимает ли [email protected]объект как действительный адрес электронной почты, но отклоняет aaaс разумным сообщением об ошибке:

self.assertFieldOutput(EmailField, {'[email protected]': '[email protected]'}, {'aaa': ['Enter a valid email address.']})
SimpleTestCase.assertFormError( ответ , форма , поле , ошибки , msg_prefix = '' )

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

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

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

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

SimpleTestCase.assertFormsetError( ответ , набор форм , индекс_форм , поле , ошибки , msg_prefix = '' )

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

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

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

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

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

SimpleTestCase.assertContains( ответ , текст , count = None , status_code = 200 , msg_prefix = '' , html = False )

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

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

SimpleTestCase.assertNotContains( ответ , текст , status_code = 200 , msg_prefix = '' , html = False )

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

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

SimpleTestCase.assertTemplateUsed( Ответ , TEMPLATE_NAME , msg_prefix = '' , не считать = None )

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

Имя представляет собой строку, например 'admin/index.html'.

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

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

with self.assertTemplateUsed('index.html'):
    render_to_string('index.html')
with self.assertTemplateUsed(template_name='index.html'):
    render_to_string('index.html')
SimpleTestCase.assertTemplateNotUsed( Ответ , 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( ответ , ожидаемый_url , status_code = 302 , target_status_code = 200 , msg_prefix = '' , fetch_redirect_response = True )

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

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

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

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

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

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

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

Следующие ниже примеры являются действительными тестами и ни к чему не приводят 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 = Нет )

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

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

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

SimpleTestCase.assertXMLNotEqual( xml1 , xml2 , msg = Нет )

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

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

SimpleTestCase.assertInHTML( иголка , стог сена , count = None , msg_prefix = '' )

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

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

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

SimpleTestCase.assertJSONEqual( необработанные , ожидаемые_данные , сообщение = Нет )

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

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

SimpleTestCase.assertJSONNotEqual( необработанные , ожидаемые_данные , сообщение = Нет )

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

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

TransactionTestCase.assertQuerysetEqual( qs , значения , преобразование = Нет , упорядочено = Истина , сообщение = Нет )

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

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

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

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

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

Значение transformаргумента по умолчанию было изменено на None.

Новое в Django 3.2:

Добавлена ​​поддержка прямого сравнения между наборами запросов.

Не рекомендуется с версии 3.2: Если transformне указан и valuesпредставляет собой список строк, он сравнивается со списком, созданным путем применения repr()к каждому члену qs. Такое поведение устарело и будет удалено в Django 4.1. Если вам это нужно, явно установите transformзначение repr.

TransactionTestCase.assertNumQueries( число , функция , * аргументы , ** kwargs )

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

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

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

Если вы хотите вызвать функцию с usingпараметром, вы можете сделать это, заключив вызов в a, 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 3.1.

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

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

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

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

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

  • followПараметр не поддерживается.

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

    >>> 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объект для своего, requestа не для того, WSGIRequestчто создает обычный клиент.

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

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

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

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

class MyTests(TestCase):

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

Почтовые службы

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

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

django.core.mail.outbox

Во время выполнения теста каждое исходящее электронное письмо сохраняется в формате django.core.mail.outbox. Это список всех EmailMessageотправленных экземпляров. outboxАтрибут является специальным атрибутом , который создается только при 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.',
            '[email protected]', ['[email protected]'],
            fail_silently=False,
        )

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

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

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

from django.core import mail

# Empty the test outbox
mail.outbox = []

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

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

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

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

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

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

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

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

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

skipIfDBFeature( * feature_name_strings )

Пропустите оформленный тест или, 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

Copyright ©2021 All rights reserved