Инструменты для тестирования¶
Django предоставляет несколько инструментов, которые могут быть полезны при написании тестов.
Тестовый клиент¶
Тестовый клиент — это класс Python, который действует как фиктивный веб-браузер, позволяя вам тестировать ваши представления и программно взаимодействовать с вашим приложением на базе Django.
Некоторые вещи, которые вы можете делать с тестовым клиентом:
Эмулирует GET или POST запросы к URL-у и обрабатывает ответ – начиная от низкоуровневого HTTP (заголовки результата и код ответа) и заканчивая содержимым ответа.
Следует по цепочке редиректов (если такие есть) и проверяет URL и код ответа на каждом шаге.
Может проверять, что полученный ответ был отрендерен определенным шаблоном Django с контекстом, который содержит определенные переменные.
Обратите внимание, этот клиент не может заменить Selenium или другие фреймворки, которые используют движок браузера для запросов. У тестового клиента Django другие задачи. Если кратко:
Вы можете использовать тестовый клиент, если нужно проверить какой шаблон использовался для рендеринга ответа, и какой контекст ему передавался.
Используйте встроенные в браузер фреймворки, такие как Selenium, для тестирования рендерингового HTML и поведения веб-страниц, а именно функциональности JavaScript. Django также обеспечивает специальную поддержку этих фреймворков; дополнительную информацию смотрите в разделе
LiveServerTestCase.
A comprehensive test suite should use a combination of both test types.
Обзор и примеры¶
To use the test client, instantiate django.test.Client and retrieve
Web pages:
>>> 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. Это помогает ускорить выполнение модульных тестов.
When retrieving pages, remember to specify the path of the URL, not the whole domain. For example, this is correct:
>>> c.get('/login/')
This is incorrect:
>>> c.get('https://www.example.com/login/')
Тестовый клиент не способен получать веб-страницы, не созданные на основе вашего проекта Django. Если вам нужно получить другие веб-страницы, используйте модуль стандартной библиотеки Python, например
urllib.При обработке URL-ов тестовый клиент использует URLconf указанный в настройке
ROOT_URLCONF.Несмотря на то, что примеры выше работают в консоли Python, некоторый функционал тестового клиента, в частности связанный с шаблонизатором, работает только при выполнении тестов.
Причина в «темной магии», которую Django использует при запуске тестов для определения какой шаблон был использован представлением. Эта «темная магия» (патчинг системы шаблонов Django в памяти) работает только при выполнении тестов.
По умолчанию тестовый клиент отключает все CSRF проверки на вашем проекте.
If, for some reason, you want the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks. To do this, pass in the
enforce_csrf_checksargument when you construct your client:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Выполнение запросов¶
Используйте класс django.test.Client для выполнения запросов.
- class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults)¶
It requires no arguments at time of construction. However, you can use keyword arguments to specify some default headers. For example, this will send a
User-AgentHTTP header in each request:>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
The values from the
extrakeyword arguments passed toget(),post(), etc. have precedence over the defaults passed to the class constructor.Аргумент
enforce_csrf_checksможет использоваться для проверки CSRF (смотрите выше).Аргумент
json_encoderпозволяет установить собственный кодировщик JSON для сериализации JSON, описанный вpost().Аргумент «raise_request_Exception» позволяет контролировать, должны ли исключения, возникающие во время запроса, также вызываться в тесте. По умолчанию установлено
True.New in Django 3.0:The
raise_request_exceptionargument was added.Создав экземпляр
Client, вы можете использовать следующие методы:- get(path, data=None, follow=False, secure=False, **extra)¶
Выполняет GET запрос по указанному
pathи возвращает объект ``Response``(описан ниже).The key-value pairs in the
datadictionary are used to create a GET data payload. For example:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
…will result in the evaluation of a GET request equivalent to:
/customers/details/?name=fred&age=7
The
extrakeyword arguments parameter can be used to specify headers to be sent in the request. For example:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_ACCEPT='application/json')
… отправит HTTP-заголовок
HTTP_ACCEPTв представление подробностей, что является хорошим способом проверки путей кода, использующих методdjango.http.HttpRequest.accepts().CGI specification
The headers sent via
**extrashould follow CGI specification. For example, emulating a different «Host» header as sent in the HTTP request from the browser to the server should be passed asHTTP_HOST.If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, the previous GET request could also be posed as:
>>> c = Client() >>> c.get('/customers/details/?name=fred&age=7')
If you provide a URL with both an encoded GET data and a data argument, the data argument will take precedence.
Если передать
followсо значениемTrue, тестовый клиент будет следовать всем редиректам и атрибутredirect_chainбудет содержать кортеж из всех URL-ов и статусов ответа.If you had a URL
/redirect_me/that redirected to/next/, that redirected to/final/, this is what you’d see:>>> response = c.get('/redirect_me/', follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]
Если передать
secureсо значениемTrue, тестовый клиент эмулирует HTTPS запрос.
- post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)¶
Выполняет POST запрос по указанному
pathи возвращает объект ``Response``(описан ниже).The key-value pairs in the
datadictionary are used to submit POST data. For example:>>> c = Client() >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
…will result in the evaluation of a POST request to this URL:
/login/
…with this POST data:
name=fred&passwd=secret
Если вы предоставляете
content_typeкак application/json,данныесериализуются с использованиемjson.dumps(), если это словарь, список или кортеж. Сериализация выполняется с помощьюDjangoJSONEncoderпо умолчанию и может быть переопределена, предоставив аргументjson_encoderдляClient. Эта сериализация также происходит для запросовput(),patch()иdelete().Если указать
content_type(например text/xml для загрузки XML), содержимоеdataбудет отправлено без изменений, используя значениеcontent_typeкак HTTP заголовокContent-Type.Если
content_typeне указан, данные изdataбудут отправлены с типом multipart/form-data. В этом случае значения изdataбудут кодированы как «multipart» сообщение и отправлены через POST.Чтобы отправить несколько значений для одного ключа – например, указать список значений из
<select multiple>– укажите список или кортеж значений. Например, следующийdataотправит три значения для поля с названиемchoices:{'choices': ('a', 'b', 'd')}
Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. For example:
>>> c = Client() >>> with open('wishlist.doc') as fp: ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
(The name
attachmenthere is not relevant; use whatever name your file-processing code expects.)You may also provide any file-like object (e.g.,
StringIOorBytesIO) as a file handle. If you’re uploading to anImageField, the object needs anameattribute that passes thevalidate_image_file_extensionvalidator. For example:>>> from io import BytesIO >>> img = BytesIO(b'mybinarydata') >>> img.name = 'myimage.jpg'
Обратите внимание, если вы хотите использовать один файл для нескольких вызовов
post(), необходимо явно сбросить указатель текущей позиции файла между вызовами. Самый простой способ – закрыть файл после вызоваpost(), как это сделано в примере выше.Вы также должны отрыть файл с возможностью чтения. Если файл содержит бинарные данные, например изображение, необходимо открыть его в режиме
rb(бинарное чтение).The
extraargument acts the same as forClient.get().If the URL you request with a POST contains encoded parameters, these parameters will be made available in the request.GET data. For example, if you were to make the request:
>>> 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)¶
Makes a HEAD request on the provided
pathand returns aResponseobject. This method works just likeClient.get(), including thefollow,secureandextraarguments, except it does not return a message body.
- options(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)¶
Выполняет OPTIONS запрос по указанному
pathи возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.Если передать
data, значение будет использовать как тело запроса, а заголовокContent-Typeравенcontent_type.The
follow,secureandextraarguments act the same as forClient.get().
- put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)¶
Выполняет PUT запрос по указанному
pathи возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.Если передать
data, значение будет использовать как тело запроса, а заголовокContent-Typeравенcontent_type.The
follow,secureandextraarguments act the same as forClient.get().
- patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)¶
Выполняет PATCH запрос по указанному
pathи возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.The
follow,secureandextraarguments act the same as forClient.get().
- delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)¶
Выполняет DELETE запрос по указанному
pathи возвращает объект ``Response``(описан ниже). Удобен при тестировании REST API.Если передать
data, значение будет использовать как тело запроса, а заголовокContent-Typeравенcontent_type.The
follow,secureandextraarguments act the same as forClient.get().
- trace(path, follow=False, secure=False, **extra)¶
Выполняет TRACE запрос по указанному
pathи возвращает объектResponse. Удобен при тестировании.Unlike the other request methods,
datais not provided as a keyword parameter in order to comply with RFC 7231 Section 4.3.8, which mandates that TRACE requests must not have a body.The
follow,secure, andextraarguments act the same as forClient.get().
- login(**credentials)¶
Если ваш сайт использует систему авторизации Django, вы можете использовать метод
login()тестового клиента, чтобы эмулировать авторизацию пользователями.После вызова этого метода тестовый клиент будет содержать все куки и данные сессии, которые необходимы, чтобы пройти проверку авторизации в представлении.
The format of the
credentialsargument depends on which authentication backend you’re using (which is configured by yourAUTHENTICATION_BACKENDSsetting). If you’re using the standard authentication backend provided by Django (ModelBackend),credentialsshould be the user’s username and password, provided as keyword arguments:>>> c = Client() >>> c.login(username='fred', password='secret') # Now you can access a view that's only available to logged-in users.
Для других бэкендов авторизации параметры могут отличаться. Это зависит от аргументов, которые принимает метод
authenticate()бэкенда авторизации.login()возвращаетTrue, если пользователь успешно залогинен.Не забудьте создать пользователя перед вызовом метода. Как мы уже сказали выше, тесты используют тестовую базу данных, которая не содержит пользователей по умолчанию. Поэтому пользователь, который существует в вашей базе данных, не будет работать в тестах. Вы должны создать пользователя в текущем наборе тестов явно (используя API Django моделей), или используя тестовые фикстуры. Помните, если вы хотите установить пароль тестовому пользователю, вы не можете этого сделать просто назначив атрибут – вы должны использовать метод
set_password(), которая установит пароль в правильно хешированном виде. Также вы можете использовать методcreate_user()для создания пользователя с правильным паролем.
- force_login(user, backend=None)¶
Если ваш сайт использует систему авторизации Django, вы можете использовать метод
force_login()чтобы эмулировать авторизацию пользователя на вашем сайте. Используйте этот метод вместоlogin(), если необходимо авторизировать пользователя в тестах и при этом не важен способ авторизации.В отличии от
login()этот метод пропускает этапы авторизации и проверки пользователя: неактивные пользователи (is_active=False) могут быть авторизированы и нет надобности указывать данные для авторизации(прим. пер. логин/пароль).Атрибут
backendпользователя будет установлен в значение аргументаbackend(которые должен содержать путь для импорта Python), илиsettings.AUTHENTICATION_BACKENDS[0], если аргумент не указан. Обычно этот атрибут устанавливает функцияauthenticate(), вызываемаяlogin().Этот метод быстрее
login()т.к. пропускаются сложные алгоритмы хэширования паролей. Также вы можете ускоритьlogin(), используя более простые алгоритмы хэширования паролей.
- logout()¶
Если ваш сайт использует систему авторизации Django, вы можете использовать метод
logout()чтобы эмулировать логаут пользователя.После вызова метода тестовый клиент очистит все куки и сессионные данные. Последующие запросы будт выполнены от
AnonymousUser.
Тестовые ответы¶
Методы get() и post() возвращают объект Response. Этот объект Response отличается от объекта HttpResponse, который возвращается представлениями Django. Тестовый объект ответа содержит дополнительные данные, которые могут быть полезны при тестировании.
Объект Response содержит следующие атрибуты:
- class Response¶
- client¶
Тестовый клиент, который отправил запрос.
- content¶
Содержимое ответа в виде байтовой строки. Окончательное содержимое страницы, которую вернуло представление, или содержимое об ошибке.
- context¶
Экземпляр
Context, который использовался при рендеринге шаблона, которые использовался при формировании ответа.Если использовалось несколько шаблонов,
contextбудет содержать список объектовContextв порядке, котором они использовались при рендеринге.Regardless of the number of templates used during rendering, you can retrieve context values using the
[]operator. For example, the context variablenamecould be retrieved using:>>> response = client.get('/foo/') >>> response.context['name'] 'Arthur'
Не используйте шаблоны Django?
Этот атрибут используется только бэкендом
DjangoTemplates. Если вы используете другой шаблонизатор, возможно вам поможетcontext_data.
- exc_info¶
- New in Django 3.0.
Кортеж из трех значений, предоставляющий информацию о необработанном исключении, если таковое возникло во время просмотра.
Значения (тип, значение, обратная трассировка) такие же, как и возвращаемые Python
sys.exc_info(). Их значения таковы:тип: тип исключения.
значение: Экземпляр исключения.
traceback: объект трассировки, который инкапсулирует стек вызовов в той точке, где изначально произошло исключение.
Если никакого исключения не произошло, то exc_info будет иметь значение None.
- json(**kwargs)¶
The body of the response, parsed as JSON. Extra keyword arguments are passed to
json.loads(). For example:>>> response = client.get('/foo/') >>> response.json()['name'] 'Arthur'
Если заголовок
Content-Typeне"application/json", будет вызвано исключениеValueError, при попытке вызывать этот метод.
- request¶
Запрос, который был отправлен.
- wsgi_request¶
Объект
WSGIRequest, который был сгенерирован при отправке запроса.
- status_code¶
HTTP статус ответа, число. Полный список возможных HTTP статусов можно найти в спецификации RFC 2616 Section 10.
- templates¶
Список объектов
Template, которые использовались при формировании ответа, в порядке рендеринга. Название файла шаблона можно получить через атрибутtemplate.name, если шаблон был загружен с файла. (Название будет строкой, например'admin/index.html'.)Не используйте шаблоны Django?
Этот атрибут содержит значение только при использовании
DjangoTemplates. Если вы используете другой шаблонизатор, возможно вам поможетtemplate_name.
- resolver_match¶
Экземпляр класса
ResolverMatchдля ответа. Вы можете использовать атрибутfunc, например, для проверки представления, которое выдало ответ:# my_view here is a function based view self.assertEqual(response.resolver_match.func, my_view) # class-based views need to 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.
You can also use dictionary syntax on the response object to query the value
of any settings in the HTTP headers. For example, you could determine the
content type of a response using response['Content-Type'].
Исключения¶
Если тестовый клиент выполнит запрос к представлению, которые вызывает исключение, исключение будет доступно в тесте. Вы можете использовать стандартный блок try ... except, или assertRaises(), чтобы протестировать исключения.
Единственные исключения, которые не передаются в тест, Http404, PermissionDenied, SystemExit, and SuspiciousOperation. Django перехватывает их и конвертирует в соответствующий код ответа HTTP. В таком случае вы можете проверять response.status_code в тесте.
Если Client.raise_request_Exception имеет значение False, тестовый клиент вернет ответ 500, как если бы он был возвращен браузеру. Ответ имеет атрибут exc_info, предоставляющий информацию о необработанном исключении.
Сохранение состояния¶
Тестовый клиент сохраняет состояние между запросами. Если ответ устанавливает куки, они будут сохранены в тестовом клиенте и отправлены в последующих get() и post() запросах.
Клиент не учитывает срок действия кук. Если необходимо удалить куку, делайте это явно, или создайте новый экземпляр Client (таким образом вы удалите все куки).
A test client has two attributes that store persistent state information. You can access these properties as part of a test condition.
- Client.cookies¶
Объект
SimpleCookie, который содержит текущие куки клиента. Подробности смотрите в документации модуляhttp.cookies.
- Client.session¶
Объект с API словаря, который содержит данные сессии. Подробности смотрите в в разделе о сессии.
Используйте переменную, чтобы изменить и сохранить данные сессии (т.к. при каждом обращении к атрибуту создается новый экземпляр
SessionStore):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
Установка языка¶
При тестировании приложений, поддерживающих интернационализацию и локализацию, вам может потребоваться установить язык для запроса тестового клиента. Способ сделать это зависит от того, включен ли LocaleMiddleware.
Если промежуточное программное обеспечение включено, язык можно установить, создав файл cookie с именем LANGUAGE_COOKIE_NAME и значением кода языка:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
или включив HTTP-заголовок Accept-Language в запрос:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробная информация находится в Как Django определяет языковую настройку.
Если промежуточное программное обеспечение не включено, активный язык можно установить с помощью translation.override():
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Более подробная информация находится в явной настройке-активного-языка.
Примеры¶
Пример простого теста с использованием тестового клиента:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
См.также
Базовые классы для создания тестов¶
Обычно тесты в Python наследуются от unittest.TestCase. Django предоставляет дополнительные классы, которые предоставляют дополнительные возможности:
Иерархия классов для тестов в Django¶
Преобразовать unittest.TestCase в Django TestCase очень просто: замените базовый класс теста с 'unittest.TestCase' на 'django.test.TestCase'. Все стандартные возможности тестов в Python будут доступны, но в дополнение к ним при запуске теста:
SimpleTestCase¶
- class SimpleTestCase¶
Наследуется от unittest.TestCase предоставляет некоторые дополнительные возможности:
Предоставляет возможность:
Проверять, что функция
вызывает определенное исключение.Проверять, что функция
вызывает определенное исключение.Проверять
поля формы.Проверять, что
при рендеринге ответа использовался определенный ответ.Сравнение
фрагментов JSON.Verifying a HTTP
redirectis performed by the app.Сравнение
фрагментов HTML, иливхождение одного фрагмента в другой.Сравнение
фрагментов XML.Сравнение
фрагментов JSON.
Позволяет запускать тест с измененными настройками.
используйте TransactionTestCase или TestCase.
- SimpleTestCase.databases¶
SimpleTestCaseпо умолчанию запрещает выполнять запросы в базу данных. Это позволяет предотвратить выполнение запросов, которые могут повлиять на другие тесты т.к.SimpleTestCaseне создает отдельную транзакцию на каждый тест. Если вас не заботит эта проблема, вы можете отключить такое поведение, указав в атрибуте классаallow_database_queriesзначениеTrue.
Предупреждение
SimpleTestCase и его потомки (т.е., TestCase, …) используют методы setUpClass() и tearDownClass() для выполнения некоторой инициализации в рамках класса (т.е., для переопределения настроек). Если вам понадобится переопределить эти методы, не забудьте воспользоваться оператором super:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
Обязательно учитывайте поведение Python, если во время setUpClass() возникает исключение. Если это произойдет, ни тесты в классе, ни tearDownClass() не запускаются. В случае django.test.TestCase это приведет к утечке транзакции, созданной в super(), что приведет к различным симптомам, включая ошибку сегментации на некоторых платформах (сообщается о macOS). Если вы хотите намеренно вызвать исключение, такое как unittest.SkipTest в setUpClass(), обязательно сделайте это перед вызовом super(), чтобы избежать этого.
The debug() method was implemented to allow running a test without
collecting the result and catching exceptions.
TransactionTestCase¶
- class TransactionTestCase¶
TransactionTestCase наследуется от SimpleTestCase.
Сброс базы данных в известное состояние в начале каждого теста, чтобы облегчить тестирование и использование ORM.
fixturesдля базы данных.Тесты, которые выполняют с учетом возможностей базы данных.
The remaining specialized
assert*methods.
Класс Django TestCase (описан ниже) использует механизм транзакций базы данных для сброса состояния базы данных перед каждым тестом. Но вы не можете тестировать коммит и отмену транзакций Django в классе TestCase. Например, вы не можете проверить, что блок кода выполняется внутри транзакции, как это требуется при использовании метода select_for_update(). В таких случаях, вы должны использовать TransactionTestCase.
TransactionTestCase и TestCase отличаются лишь механизмом сброса базы данных перед каждым тестом и мозможностью тестировать транзакции:
TransactionTestCaseсбрасывает состояние базы данных после выполнения тестам путем очистки всех таблиц.TransactionTestCaseпозволяет вызывать коммит и отмену транзакций и проверять результат в базе данных.TestCase, в свою очередь, не очищает все таблицы в конце теста. Вместо этого, каждый тест оборачивается в транзакцию, которые откатывается по завершению теста. Это гарантирует, что откат в конце теста вернёт базу данных в начальное состояние.
Предупреждение
TestCase, который выполняется на базе данных, которая не поддерживает транзакции (например, MySQL с MyISAM), и все TransactionTestCase, после завершения теста очищают таблицы.
Данные приложения не будут перезагружены; если это необходимо (например, распространяемые приложения должны использовать это) вы можете указать serialized_rollback = True для класса TestCase.
TestCase¶
- class TestCase¶
Это наиболее распространенный класс, используемый для написания тестов в Django. Он наследуется от TransactionTestCase (и расширения SimpleTestCase). Если ваше приложение Django не использует базу данных, используйте SimpleTestCase.
Класс:
Обертывает тесты в два вложенных блока
atomic(): один для всего класса и один для каждого теста. Поэтому, если вы хотите протестировать какое-то конкретное поведение транзакции базы данных, используйтеTransactionTestCase.Проверяет отложенные ограничения базы данных в конце каждого теста.
Он также предоставляет дополнительный метод:
- classmethod TestCase.setUpTestData()¶
Описанный выше
atomicблок уровня класса позволяет создание начальных данных на уровне класса, один раз для всегоTestCase. Такой подход пригодится для быстрых тестов, по сравнению с обычным использованием методаsetUp().Например:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...
Следует отметить, что если тесты запущены на базе данных, у которой нет поддержки транзакций (например, MySQL с движком MyISAM), то метод
setUpTestData()будет вызван перед выполнением каждого теста, ухудшая показатели скорости.Be careful not to modify any objects created in
setUpTestData()in your test methods. Modifications to in-memory objects from setup work done at the class level will persist between test methods. If you do need to modify them, you could reload them in thesetUp()method withrefresh_from_db(), for example.
LiveServerTestCase¶
- class LiveServerTestCase¶
LiveServerTestCase работает как и TransactionTestCase, но при этом в фоне запускается встроенный сервер Django, который по завершению тестов выключается. Это позволяет использовать клиенты для автоматического тестирования вместо тестового клиента Django, такие как, например, клиент Selenium. С их помощью вы можете создавать функциональные тесты и симулировать реальное поведение пользователей.
По умолчанию сервер слушает localhost, выбирая первый свободный порт из 8081-8179. Полный URL к серверу можно получить в тестах через атрибут self.live_server_url.
To demonstrate how to use LiveServerTestCase, let’s write a Selenium test.
First of all, you need to install the selenium package into your Python
path:
$ python -m pip install selenium
...\> py -m pip install selenium
Теперь добавьте LiveServerTestCase-тесты в ваше приложение (например: myapp/tests.py). В нашем примере мы используем приложение staticfiles и хотим, чтобы статические файлы были доступны во время тестов, как это работает при использовании сервера для разработки с настройкой DEBUG=True, то есть без использования collectstatic. Мы будем использовать класс StaticLiveServerTestCase, который предоставляет такую возможность. Если вам не нужен такой функционал, замените его на django.test.LiveServerTestCase.
Пример тестов:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Теперь вы можете запустить тесты:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
Этот тест автоматически откроет Firefox, затем страницу авторизации, вводит логин и пароль и нажимает кнопку «Log in». Selenium поддерживает и другие драйверы, если у вас не установлен Firefox, или вы хотите использовать другие браузеры. Пример выше показывает лишь малую часть возможностей Selenium; подробности смотрите в документации.
Примечание
При использовании размещённой в оперативной памяти SQLite для выполнения тестов, подключение к базе данных будет использоваться параллельно двумя потоками: в одном потоке работает тестовый сервер, во втором выполняются тесты. Важно, чтобы запросы в разных потоках не выполнялись одновременно, т.к. это может привести к неожиданному поведению тестов. Поэтому вам необходимо убедиться, что разные потоки не обращаются к базе данных одновременно. Для этого в некоторых ситуациях (например, после нажатия на ссылку или отправки формы), вам необходимо дождаться пока Selenium получит ответ от сервера, и загрузится новая страница, затем продолжить выполнение тестов. Вы можете сделать это указав Selenium ждать пока не будет найден тег <body> в ответе (требуется Selenium > 2.13):
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element_by_tag_name('body'))
Хитрость здесь в том, что на самом деле не существует такого понятия, как «загрузка страницы», особенно в современных веб-приложениях, которые динамически генерируют HTML после того, как сервер сгенерировал исходный документ. Таким образом, проверка наличия <body> в ответе может не обязательно подходить для всех случаев использования. Пожалуйста, обратитесь к разделу «Часто задаваемые вопросы по Selenium» и «Документации по Selenium» для получения дополнительной информации.
Возможности приложения для тестирования¶
Тестовый клиент по умолчанию¶
- SimpleTestCase.client¶
Каждый тест экземпляра django.test.*TestCase может использовать экземпляр тестового клиента Django, обратившись к атрибуту self.client. Этот клиент пересоздается для каждого теста, поэтому вам не нужно беспокоится о состоянии (например о куках) между тестами.
Поэтому вместо создания экземпляра Client в каждом тесте:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
…вы можете просто обращаться к self.client:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
Переопределение тестового клиента¶
- SimpleTestCase.client_class¶
Если вы хотите использовать переопределенный класс Client, укажите его в атрибуте client_class:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Загрузка фикстур¶
- TransactionTestCase.fixtures¶
A test case for a database-backed website isn’t much use if there isn’t any
data in the database. Tests are more readable and it’s more maintainable to
create objects using the ORM, for example in TestCase.setUpTestData(),
however, you can also use fixtures.
Фикстуры - это набор данных, которые Django умеет импортировать в базу данных. Например, если ваш сайт использует модель пользователя, вы можете создать фикстуру с данными пользователей и загружать её перед запуском тестов.
Самый простой способ создать фикстуры, использовать команду manage.py dumpdata. Необходимые данные должны быть в вашей базе данных. Подробности смотрите в описании команды dumpdata.
После создания фикстур, добавьте их в каталог fixtures одного из ваших приложений из INSTALLED_APPS. Теперь вы можете использовать их в тестах, указав в атрибуте fixtures класса-наследника django.test.TestCase:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Что произойдет:
Перед выполнение теста и перед методом
setUp(), Django очитсти базу данных и вернет её до состояния после выполнения командыmigrate.Then, all the named fixtures are installed. In this example, Django will install any JSON fixture named
mammals, followed by any fixture namedbirds. See theloaddatadocumentation for more details on defining and installing fixtures.
Перезагрузка фикстур выполняет перед каждым тестом, вы можете не беспокоится, что один тест повлияет на выполнение другого.
По умолчанию фикстуры загружаются в базу данных default. Если вы используете несколько баз данных и указали атрибут multi_db=True, фикстуры будут загружены в несколько баз данных.
Конфигурация URLconf¶
Если выше приложение содержит представления, вам понадобятся тесты, которые вызывают их, используя тестовый клиент. Однако, пользователь приложения может добавить представления на любой URL. Это означает, что тесты не могут полагаться на то, что представление доступно по определенному URL-у.
Поддержка нескольких баз данных¶
- TransactionTestCase.databases¶
Django устанавливает тестовую базу данных, соответствующую каждой базе данных, которая определена в определении DATABASES в ваших настройках и на которую ссылается хотя бы один тест через базы данных.
Django создает тестовую базу для каждой указанной базы данных из настройки DATABASES. Однако, большую часть времени выполнения тестов занимает операция очистки базы данных(flush) перед каждым тестом. Если вы используете несколько баз данных, требуется несколько операций flush (одна на каждую базу денных). Это может заниматься достаточно много времени.
Для оптимизации Django очищает только базу данных default перед каждым тестом. Если ваш проект использует несколько баз данных, и в перед тестом необходимо очищать все базы данных, вы можете указать атрибут multi_db для класса тестов.
Например:
class TestMyViews(TransactionTestCase):
databases = {'default', 'other'}
def test_index_page_view(self):
call_some_test_code()
This test case will flush the default and other test databases before
running test_index_page_view. You can also use '__all__' to specify
that all of the test databases must be flushed.
Флаг multi_db также определяет в какую базу данных будет загружены фикстуры из TransactionTestCase.fixtures. По умолчанию (при multi_db=False) фикстуры загружаются только в базу данных default. При multi_db=True фикстуры загружаются во все базы данных.
Запросы к базам данных, не входящим в «базы данных», будут выдавать ошибки утверждения, чтобы предотвратить утечку состояния между тестами.
- TestCase.databases¶
По умолчанию только база данных по умолчанию будет заключена в транзакцию во время выполнения TestCase, а попытки запросить другие базы данных приведут к ошибкам утверждения, чтобы предотвратить утечку состояния между тестами.
Используйте атрибут класса databases в тестовом классе, чтобы запросить перенос транзакций для баз данных, отличных от баз данных по умолчанию.
Например:
class OtherDBTests(TestCase):
databases = {'other'}
def test_other_db_query(self):
...
Этот тест разрешает запросы только к «другой» базе данных. Как и в случае с SimpleTestCase.databases и TransactionTestCase.databases, константа '__all__' может использоваться для указания того, что тест должен разрешать запросы ко всем базам данных.
Переопределение настроек¶
Предупреждение
Используйте приведённые ниже функции для временного изменения значения параметров конфигурации в тестах. Не изменяйте django.conf.settings напрямую, так как Django не восстановит оригинальные значения после таких манипуляций.
- SimpleTestCase.settings()¶
При тестировании часто необходимо временно переопределить настройки и вернуть начальные значение после завершения тестов. Для таких случаев Django предоставляет стандартный контекстный менеджер Python (смотрите PEP 343), который называется settings(). Его можно использовать следующим образом:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
This example will override the LOGIN_URL setting for the code
in the with block and reset its value to the previous state afterwards.
- SimpleTestCase.modify_settings()¶
Помогает при переопределении настроек, которые содержат список. Если вам необходимо добавить или удалить значение из списка, используйте контекстный менеджер modify_settings():
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
'remove': [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
],
}):
response = self.client.get('/')
# ...
Для каждого действия вы можете указать список значений или строку. Если значение уже в списке, append и prepend не имеют никакого эффекта; аналогично remove ничего не сделает, если значение отсутствует в списке.
- override_settings()¶
Если вы хотите переопределить настройки для тестового метода, 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 илиr MiddlewareTestCase, вы обнаружите, что оригинальные классы были все равно изменены декораторами. Декоратор modify_settings() всегда применяет после override_settings(), если их добавить к одному классу.
Предупреждение
Файл настроек содержит некоторые настройки, которые используются только при инициализации Django. Если вы измените их с помощью override_settings, настройка измениться, если получить значение из модуля django.conf.settings, однако Django может обращаться к ней по другому. Поэтому override_settings() или modify_settings() могут работать не так, как вы ожидаете .
Мы не советуем изменять настройку DATABASES. Менять CACHES можно, но требует дополнительных действий, если кеш используется другими механизмами Django, например django.contrib.sessions. В таком случае вам понадобиться заново инициализировать сессию в тесте после изменения CACHES.
Также не указывайте ваши настройки в константах модуля, override_settings() не будет работать с такими настройками т.к. они выполняются только при первом импорте модуля. (FIXME: whut?)
Вы также можете протестировать отсутствие настройки, удалив ее после использования декоратора:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
При переопределении настроек учитывайте ситуации, когда выше приложение использует кеш или другие механизмы, которые сохраняют свое состояние после изменения настроек. Django предоставляет сигнал django.test.signals.setting_changed, который позволяет сбросить состояние, или выполнить другие действия, при изменении настроек.
Django использует этот сигнал для сброса различных данных:
Переопределенные настройки |
Данные, который сбрасываются |
|---|---|
USE_TZ, TIME_ZONE |
Часовой пояс баз данных |
TEMPLATES |
Шаблонные движки |
SERIALIZATION_MODULES |
Кеш сериализаторов |
LOCALE_PATHS, LANGUAGE_CODE |
Перевод по умолчанию и загруженные переводы |
MEDIA_ROOT, DEFAULT_FILE_STORAGE |
Default file storage |
Очистка тестовых электронных писем¶
Если вы используйте класс TestCase Django, содержимое отправленных тестовых электронных писем будет очищено перед каждым тестом.
Подробности смотрите ниже в разделе об отправке писем.
Проверки¶
Поскольку обычный класс Python unittest.TestCase реализует методы утверждения, такие как assertTrue() и assertEqual(), собственный класс TestCase Django предоставляет ряд пользовательских методов утверждения, которые полезны для тестирования веб-приложений:
Сообщения об ошибки можно переопределить аргументом msg_prefix для большинства методов проверки. Указанная строка будет добавлена к каждому сообщению об ошибке. Это позволяет указать вам дополнительную информацию, которая поможет определить какой тест не прошел и причину ошибки.
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)¶
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)
Проверяет, что при выполнении функции
callableвызывается исключениеexpected_exceptionс сообщениемexpected_message. Похож наassertRaisesRegex(), ноexpected_messageне регулярное выражение.Если передать только
expected_exceptionиexpected_message, вернет контекстный менеджер, что позволит писать проверяемый код без добавления в функцию:with self.assertRaisesMessage(ValueError, 'invalid literal for int()'): int('a')
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)¶
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
Аналогично
SimpleTestCase.assertRaisesMessage(), но дляassertWarnsRegex()вместоassertRaisesRegex().
- SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')¶
Проверяет поведение поля формы с разными значениями.
- Параметры:
fieldclass – класс поля, который тестируется.
valid – словарь, который указывает передаваемые значение и ожидаемые после проверки.
invalid – словарь, который указывает неправильные входные значения и ожидаемые ошибки валидации.
field_args – позиционные аргументы, которые передаются при создании поля.
field_kwargs – именованные аргументы, которые передаются при создании поля.
empty_value – ожидаемое значение после проверки для входящих пустых значений из
empty_values.
Например, следующий код проверяет, что поле
EmailFieldпринимаетa@a.comкак правильное значение, но возвращает ошибки, если передатьaaa:self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
- SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix='')¶
Asserts that a field on a form raises the provided list of errors when rendered on the form.
formis the name theForminstance was given in the template context.fieldis the name of the field on the form to check. Iffieldhas a value ofNone, non-field errors (errors you can access viaform.non_field_errors()) will be checked.errorsis an error string, or a list of error strings, that are expected as a result of form validation.
- SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix='')¶
Проверяет, что
formsetиз ответа содержит указанный список ошибок.formsetis the name theFormsetinstance was given in the template context.form_indexis the number of the form within theFormset. Ifform_indexhas a value ofNone, non-form errors (errors you can access viaformset.non_form_errors()) will be checked.fieldis the name of the field on the form to check. Iffieldhas a value ofNone, non-field errors (errors you can access viaform.non_field_errors()) will be checked.errorsis an error string, or a list of error strings, that are expected as a result of form validation.
- SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)¶
Asserts that a
Responseinstance produced the givenstatus_codeand thattextappears in the content of the response. Ifcountis provided,textmust occur exactlycounttimes in the response.Укажите
Trueвhtml, чтобыtextобрабатывался как HTML. Сравнение содержимого будет основано на семантике HTML, а не посимвольном сравнении. Пробелы будут проигнорированы в большинстве случаев, порядок атрибутов не учитывается. Подробности с мотрите в описанииassertHTMLEqual().
- SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)¶
Asserts that a
Responseinstance produced the givenstatus_codeand thattextdoes not appear in the content of the response.Укажите
Trueвhtml, чтобыtextобрабатывался как HTML. Сравнение содержимого будет основано на семантике HTML, а не посимвольном сравнении. Пробелы будут проигнорированы в большинстве случаев, порядок атрибутов не учитывается. Подробности с мотрите в описанииassertHTMLEqual().
- SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)¶
Проверяет, что указанный шаблон использовался при рендеринге ответа.
The name is a string such as
'admin/index.html'.The count argument is an integer indicating the number of times the template should be rendered. Default is
None, meaning that the template should be rendered one or more times.Можно использовать как менеджер контекста:
with self.assertTemplateUsed('index.html'): render_to_string('index.html') with self.assertTemplateUsed(template_name='index.html'): render_to_string('index.html')
- SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')¶
Проверяет, что указанный шаблон не использовался при рендеринге ответа.
Можно использовать как менеджер контекста, как и
assertTemplateUsed().
- SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')¶
Утверждает, что два URL-адреса одинаковы, игнорируя порядок параметров строки запроса, за исключением параметров с одинаковым именем. Например,
/path/?x=1&y=2равно/path/?y=2&x=1, но/path/?a=1&a=2не равно/path/?a=2&a=1.
- SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)¶
Asserts that the response returned a
status_coderedirect status, redirected toexpected_url(including anyGETdata), and that the final page was received withtarget_status_code.Если запрос использует аргумент
follow,expected_urlиtarget_status_codeбудут указывать на последний запрос.Если
fetch_redirect_responseравенFalse, последняя страница не будет загружена. Т.к. тестовый клиент не загружает внешние URL-ы, этот аргумент полезен, еслиexpected_urlуказывает на внешний URL.Протокол правильно обрабатывается при сравнении URL-ов. Если протокол не указан при редиректе, будет использоваться протокол изначального запроса.
- SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)¶
Сравнивает строки
html1иhtml2. Сравнение основано на семантике HTML. При сравнении используются следующие правила:Пробелы перед и после HTML тегов игнорируются.
Все типы пробелов считаются одинаковыми.
Все незакрытые теги закрываются, например, при закрытии внешнего тега, или в конце HTML документа.
Пустые теги равны самозакрывающиемся аналогичным тегам.
Порядок атрибутов HTML тегов не учитывается.
Attributes without an argument are equal to attributes that equal in name and value (see the examples).
Текст, ссылки на символы и ссылки на сущности, ссылающиеся на один и тот же символ, эквивалентны.
Следующие тесты не вызывают
AssertionError:self.assertHTMLEqual( '<p>Hello <b>'world'!</p>', '''<p> Hello <b>'world'! </b> </p>''' ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>' )
html1andhtml2must be valid HTML. AnAssertionErrorwill be raised if one of them cannot be parsed.Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)¶
Проверяет, что строки
html1иhtml2отличаются. Сравнение основано на семантике HTML. Подробности смотрите в описанииassertHTMLEqual().html1andhtml2must be valid HTML. AnAssertionErrorwill be raised if one of them cannot be parsed.Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)¶
Утверждает, что строки
xml1иxml2равны. Сравнение основано на семантике XML. Как и в случае сassertHTMLEqual(), сравнение производится на анализируемом содержимом, поэтому учитываются только семантические различия, а не синтаксические различия. Когда в каком-либо параметре передается недопустимый XML, всегда выдается сообщение AssertionError, даже если обе строки идентичны.Объявление XML, тип документа, инструкции по обработке и комментарии игнорируются. Сравниваются только корневой элемент и его дочерние элементы.
Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)¶
Проверяет, что строки
xml1иxml2не одинаковы. При сравнении используется семантика XML. Подробности смотрите в описанииassertXMLEqual().Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')¶
Asserts that the HTML fragment
needleis contained in thehaystackone.Если указан параметр
count, проверяется, чтоneedleвстречается указанное число раз.Whitespace in most cases is ignored, and attribute ordering is not significant. The passed-in arguments must be valid HTML.
- SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)¶
Проверяет, что JSON в
rawиexpected_dataодинаковый. Обработка пробелов в JSON делегируется библиотекеjson.Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)¶
Проверяет, что фрагменты JSON
rawиexpected_dataне одинаковы. Подробности смотрите в описанииassertJSONEqual().Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True, msg=None)¶
Asserts that a queryset
qsreturns a particular list of valuesvalues.The comparison of the contents of
qsandvaluesis performed by applyingtransformtoqs. By default, this means that therepr()of each value inqsis compared to thevalues. Any other callable can be used ifrepr()doesn’t provide a unique or helpful comparison.По умолчанию сравнение также зависит от порядка. Если
qsне обеспечивает неявное упорядочение, вы можете установить для параметраorderedзначениеFalse, что превратит сравнение в сравнениеcollections.Counter. Если порядок не определен (если заданныйqsне упорядочен и сравнение проводится с более чем одним упорядоченным значением), выдаетсяValueError.Вы можете изменить сообщение об ошибке с помощью аргумента
msg.
- TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)¶
Проверяет, что при вызове функции
funcс аргументами*argsи**kwargsвыполнилосьnumзапросов в базу данных.Если
kwargsсодержит ключ"using", он используется для определения базы данных, для которой необходимо считать количество запросов. Если вы хотите вызывать функцию с аргументомusing, оберните её вlambda, чтобы добавить аргумент:self.assertNumQueries(7, using='non_default_db')
Если
kwargsсодержит ключ"using", он используется для определения базы данных, для которой необходимо считать количество запросов. Если вы хотите вызывать функцию с аргументомusing, оберните её вlambda, чтобы добавить аргумент:self.assertNumQueries(7, lambda: my_function(using=7))
Вы можете использовать этот метод как менеджер контекста:
with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
Пропуск тестов¶
Вы можете пометить свои тесты, чтобы можно было легко запускать определенное подмножество. Например, вы можете пометить быстрые или медленные тесты:
from django.test import tag
class SampleTestCase(TestCase):
@tag('fast')
def test_fast(self):
...
@tag('slow')
def test_slow(self):
...
@tag('slow', 'core')
def test_slow_but_core(self):
...
You can also tag a test case:
@tag('slow', 'core')
class SampleTestCase(TestCase):
...
Подклассы наследуют теги от суперклассов, а методы наследуют теги от своего класса. Данный:
@tag('foo')
class SampleTestCaseChild(SampleTestCase):
@tag('bar')
def test(self):
...
SampleTestCaseChild.test будет помечен 'slow', 'core', 'bar' и 'foo'.
Затем вы можете выбрать, какие тесты запускать. Например, чтобы запускать только быстрые тесты:
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
Или запустить быстрые тесты и основной (хоть и медленный):
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
Вы также можете исключить тесты по тегу. Чтобы запустить основные тесты, если они не медленные:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag имеет приоритет над test --tag, поэтому, если тест имеет два тега и вы выбираете один из них и исключаете другой, тест не будет запущен.
Тестирование асинхронного кода¶
Если вы просто хотите протестировать выходные данные своих асинхронных представлений, стандартный тестовый клиент запустит их в собственном асинхронном цикле без каких-либо дополнительных действий с вашей стороны.
Однако если вы хотите написать полностью асинхронные тесты для проекта Django, вам необходимо принять во внимание несколько вещей.
Во-первых, ваши тесты должны быть методами async def в тестовом классе (чтобы дать им асинхронный контекст). Django автоматически обнаружит любые тесты async def и обернет их так, чтобы они выполнялись в собственном цикле событий.
Если вы тестируете асинхронную функцию, вам также необходимо использовать клиент асинхронного тестирования. Он доступен как django.test.AsyncClient или self.async_client в любом тесте.
AsyncClient has the same methods and signatures as the synchronous (normal)
test client, with two exceptions:
The
followparameter is not supported.Headers passed as
extrakeyword arguments should not have theHTTP_prefix required by the synchronous client (seeClient.get()). For example, here is how to set an HTTPAcceptheader:>>> c = AsyncClient() >>> c.get( ... '/customers/details/', ... {'name': 'fred', 'age': 7}, ... ACCEPT='application/json' ... )
При использовании AsyncClient необходимо ожидать любого метода, отправляющего запрос:
async def test_my_thing(self):
response = await self.async_client.get('/some-url/')
self.assertEqual(response.status_code, 200)
Асинхронный клиент также может вызывать синхронные представления; он выполняется через асинхронный путь запроса Django, который поддерживает оба варианта. Любое представление, вызванное через AsyncClient, получит объект ASGIRequest для своего запроса, а не WSGIRequest, который создает обычный клиент.
Предупреждение
Если вы используете тестовые декораторы, они должны быть асинхронно-совместимыми, чтобы гарантировать правильную работу. Встроенные декораторы Django будут вести себя правильно, но сторонние декораторы могут не выполняться (они «обернут» не ту часть потока выполнения, а не ваш тест).
Если вам нужно использовать эти декораторы, вам следует украсить свои тестовые методы async_to_sync() внутри них:
from asgiref.sync import async_to_sync
from django.test import TestCase
class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self):
...
Сервисы для отправки писем¶
Если выши представления оправляют электронные письма, используя встроенный функционал Django, вы, скорее всего, не захотите, чтобы они отправлялись при каждом запуске тестов. В таких случаях Django автоматически перенаправляет все письма, отправленные через механизм Django, в специальную переменную в памяти. Это позволяет тестировать отправку писем без реальной отправки.
Django неявно подменяет бэкенд для отправки писем при запуске тестов. (Не беспокойтесь – это распространяется только на Django. Если вы используете локальные почтовые сервисы, они будут работь без изменений.)
- django.core.mail.outbox¶
При выполнении тестов все отправленные письма сохраняются в переменной django.core.mail.outbox. Это просто список объектов EmailMessage писем, которые были отправлены. Атрибут outbox создается только для locmem бэкенда отправки писем. Вы не можете импортировать его из модуля django.core.mail. Пример ниже показывает как следует его использовать.
Этот пример проверяет размер и содержимое django.core.mail.outbox:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
'Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, 'Subject here')
Как упоминалось выше, содержимое этого атрибута очищается при запуске каждого теста из *TestCase. Чтобы явно очистить – просто укажите пустой список в mail.outbox:
from django.core import mail
# Empty the test outbox
mail.outbox = []
Команды управления¶
Команды управления могут быть протестированы с помощью функции call_command(). Вывод может быть перенаправлен в объект StringIO:
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())
Пропуск тестов¶
Библиотека unittest предоставляет декораторы @skipIf и @skipUnless, которые позволяют пропускать тесты, если вы знаете, что они не выполнятся успешно при определенных условиях.
Например, если ваш тест требует определенной необязательной библиотеки, вы можете обернуть тест декоратором @skipIf. Тест будет пропущен, будет выведена причина пропуска.
Django предоставляет два дополнительных декоратора для пропуска теста. Вместо проверки явного условия, эти декораторы проверяют наличие определенного функционала базы данных, и тест пропускается, если база данных не поддерживает необходимый функционал.
The decorators use a string identifier to describe database features.
This string corresponds to attributes of the database connection
features class. See django.db.backends.BaseDatabaseFeatures
class for a full list of database features that can be used as a basis
for skipping tests.
- 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