Подготовка условий для запуска тестов
Перед выполнением тестов иногда нужно подготовить определённые условия для их запуска: создать пользователя с определённым уровнем доступа или эмулировать работу браузера, чтобы хранить информацию о пользовательской сессии.
Подготовка контекста применяется в различных системах тестирования и традиционно называется tear up или setup, а функции, выполняющиеся после теста, обычно называют tear down или cleanup.
В Django и в unittest функция, выполняющаяся до начала тестов, называется setUp(), а выполняющаяся после окончания — tearDown().
В примере используется unittest.TestCase; для Django код тот же.
Скопировать кодPYTHON
from unittest import TestCase
class SimpleTest(TestCase):
def setUp(self):
print("SetUp")
def test_one(self):
print("One")
self.assertEqual(int("1"), 1)
def test_two(self):
print("Two")
self.assertTrue("2")
def tearDown(self):
print("tearDown")
Этот код можно запустить без Django. Команды print() ломают стандартный вывод, но зато позволяют понять порядок выполнения методов. Перед вызовом каждого теста запускается метод setUp(), после выполнения — tearDown().
Скопировать код
$ python -m unittest test_simple
SetUp
One
tearDown
.SetUp
Two
tearDown
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Создание тестового веб-клиента
При тестировании Django-проектов не обойтись без веб-клиента, эмулятора браузера, который будет обращаться к страницам сайта, отправляя GET- и POST-запросы, и принимать ответы сервера.
Если ответ содержит редирект, клиент сможет его отследить и считать ответ страницы, на которую осуществляется переход. Главное отличие тестового клиента от обычного браузера в том, что он имеет доступ «под капот» Django и знает, какие переменные и шаблоны были задействованы при рендеринге страницы.
В настройках проекта yatube/settings.py измените значение ключа ALLOWED_HOSTS:
Скопировать кодPYTHON
ALLOWED_HOSTS = [
"localhost",
"127.0.0.1",
"[::1]",
"testserver",
]
Это перечень адресов, с которых серверу разрешено принимать запросы. В этом списке есть и специальный адрес тестового сервера.
Теперь в интерактивном режиме можно эмулировать работу с сайтом через тестовый клиент. Надо только его создать. Для этого в модуле django.test есть класс Client: каждый экземпляр этого класса — это «как-бы-браузер», которым можно управлять из кода.
Примеры из следующего листинга не сработают в вашем проекте: скорее всего, у вас не зарегистрирован пользователь с логином terminator.
Проверить работу этого кода можно
- заменив в коде логин и пароль на данные любого зарегистрированного в вашем проекте пользователя
- или предварительно создав пользователя с логином terminator и паролем skynetMyLove.
Скопировать кодBASH
(venv) $ python manage.py shell
Python 3.8.0 (default, Nov 22 2019, 23:37:58)
[Clang 11.0.0 (clang-1100.0.33.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/auth/login/', {'username': 'terminator', 'password': 'skynetMyLove'})
>>> response.status_code
302
>>> response = c.post('/auth/login/', {'username': 'terminator', 'password': 'skynetMyLove'}, follow=True)
>>> response.status_code
200
Возможности тестового клиента
Тестовый клиент может делать стандартные HTTP-запросы всеми возможными методами (GET, POST, DELETE и пр.), получать информацию о шаблонах и о словаре context, переданному в шаблон при рендеринге, создавать и авторизовать пользователей.
Во время тестов вся работа идёт с временной копией базы, после тестирования основная база остаётся без изменений, а временная база удаляется из памяти.
client.get()
Метод .get() делает запрос к странице path и передаёт необходимые параметры: get(path, data=None, follow=False, secure=False, **extra).
Скопировать кодPYTHON
>>> c = Client()
>>> c.get('/search/', {'query': 'утро', 'page': 7})
Такое обращение эквивалентно обращению к странице /search/?query=утро&page=7. Параметр follow разрешает клиенту совершить переход, если ответ содержит редирект:
Скопировать кодPYTHON
>>> response = c.get('/go/', follow=True)
>>> response.redirect_chain
[('http://testserver/step1/', 302), ('http://testserver/end/', 302)]
client.post()
Для отправки данных методом POST у эмулятора клиента есть метод post(): post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra).
Скопировать кодPYTHON
>>> c = Client()
>>> c.post('/login/', {'name': 'terminator', 'passwd': 'skynetMyLove'})
Помните, что форма логина в Django после авторизации редиректит пользователя, поэтому будет правильно отправлять запрос с параметром follow=True.
login, logout и force_login
Вместо заполнения формы авторизации можно применить метод login: он авторизует пользователя по логину и паролю:
Скопировать кодPYTHON
>>> c = Client()
>>> c.login(username='terminator', password='skynetMyLove')
True
Если авторизация прошла успешно, то метод вернет True.
При тестировании можно авторизовать пользователя даже без пароля, обратившись к объекту пользователя методом force_login()
Скопировать кодPYHTON
>>> c = Client()
>>> user = User.objects.get(username="terminator")
>>> c.force_login(user)
True
Для тестирования событий, происходящих после выхода пользователя из системы, можно вызвать метод logout(). Но если вам это не нужно для тестирования — разлогинивать пользователей совершенно не обязательно: пользовательская сессия удаляется после завершения тестов.
Информация об обращении
На любой запрос клиента возвращается специальный response-объект. В нём содержится ответ сервера и некоторые дополнительные свойства:
client — объект клиента, который использовался для обращенияcontent — данные ответа в виде строки байтовcontext — словарь переменных, переданный для отрисовки шаблона при вызове функции render()request — объект request, первый параметр view-функцийtemplates — перечень объектов шаблонов, вызванных для отрисовки страницы, возвращаемой серверомresolver_match — специальный объект, соответствующий объекту path() из списка urlpatterns
Пример теста, который использует возможности клиента и ответов сервера:
Скопировать кодPYTHON
class ProfileTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username="sarah", email="connor.s@skynet.com", password="12345"
)
self.post = Post.objects.create(text="You're talking about things I haven't done yet in the past tense. It's driving me crazy!", author=self.user)
def test_profile(self):
response = self.client.get("/sarah/")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["posts"]), 1)
self.assertIsInstance(response.context["profile"], User)
self.assertEqual(response.context["profile"].username, self.user.username)
Особенности работы с базой данных
При тестировании Django создает временную версию базы в памяти, а не обращается к существующей базе. Все записи и изменения, которые вносятся в базу во время тестов, производятся именно с этой временной версией, а основная база проекта остаётся в исходном состоянии.
При обычной работе в интерактивном режиме вы обращаетесь к базе реального проекта, а во время запуска тестов — ко временной базе.