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

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 .

Новое в Django 3.0:

raise_request_exception Аргумент был добавлен.

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

get( path , data = None , follow = False , secure = False , ** дополнительно )

Делает запрос 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 (например, text / xml для полезной нагрузки XML), содержимое data отправляется как есть в запросе POST с использованием content_type в Content-Type заголовке HTTP .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

head( path , data = None , follow = False , secure = False , ** дополнительно )

Делает запрос 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 объект , возвращаемый видом Джанго; объект тестового ответа содержит некоторые дополнительные данные, полезные для проверки тестового кода.

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

класс Response
client

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

content

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

context

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

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

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

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

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

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

exc_info
Новое в Django 3.0.

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

Значения (тип, значение, трассировка) такие же, как возвращаемые 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 исключение.

Вы также можете использовать синтаксис словаря в объекте ответа, чтобы запросить значение любых параметров в заголовках HTTP. Например, вы можете определить тип содержимого ответа, используя response['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 для предоставления информации о необработанном исключении.

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

Тестовый клиент отслеживает состояние. Если ответ возвращает куки, то , что куки будут храниться в тестовом клиенте и отправляется со всеми последующими 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.SkipTest in 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 = True TestCase

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

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

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 install selenium

Затем добавьте 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 dumpdata dumpdata documentation

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

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

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

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

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

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

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

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

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

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

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

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

TransactionTestCase.databases

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

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

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

Например:

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

    def test_index_page_view(self):
        call_some_test_code()

Этот тестовый пример перед запуском очистит базы данных default и other test 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( ожидаемое_исключение , ожидаемое_сообщение , вызываемый , * аргументы , ** 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 , код_статуса = 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 = None )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

TransactionTestCase.assertQuerysetEqual( qs , значения , преобразование = repr , заказанный = True , msg = None )

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

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

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

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

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 def async def

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

За исключением follow неподдерживаемого параметра, он 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