Подготовка условий для запуска тестов

Перед выполнением тестов иногда нужно подготовить определённые условия для их запуска: создать пользователя с определённым уровнем доступа или эмулировать работу браузера, чтобы хранить информацию о пользовательской сессии.
Подготовка контекста применяется в различных системах тестирования и традиционно называется tear up или setup, а функции, выполняющиеся после теста, обычно называют tear down или cleanup.
В Django и в unittest функция, выполняющаяся до начала тестов, называется setUp(), а выполняющаяся после окончания — tearDown().
В примере используется unittest.TestCase; для Django код тот же.
Скопировать кодPYTHON
# file: test_simple.py 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.
Проверить работу этого кода можно
Скопировать код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() # — Браузер, сделай POST-запрос к странице логина # и передай значения переменных username и password! >>> response = c.post('/auth/login/', {'username': 'terminator', 'password': 'skynetMyLove'}) # какой код вернула страница при запросе? >>> response.status_code 302 # после авторизации Django переадресует пользователя, # но в клиенте-эмуляторе по умолчанию переадресация запрещена, потому вернулся статус 302 # разрешим переадресацию: follow=True >>> response = c.post('/auth/login/', {'username': 'terminator', 'password': 'skynetMyLove'}, follow=True) >>> response.status_code 200 # Терминатор успешно залогинился, страница вернула код 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-объект. В нём содержится ответ сервера и некоторые дополнительные свойства:
Пример теста, который использует возможности клиента и ответов сервера:
Скопировать кодPYTHON
class ProfileTest(TestCase): def setUp(self): # создание тестового клиента — подходящая задача для функции setUp() 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): # формируем GET-запрос к странице сайта response = self.client.get("/sarah/") # проверяем что страница найдена self.assertEqual(response.status_code, 200) # проверяем, что при отрисовке страницы был получен список из 1 записи self.assertEqual(len(response.context["posts"]), 1) # проверяем, что объект пользователя, переданный в шаблон, # соответствует пользователю, которого мы создали self.assertIsInstance(response.context["profile"], User) self.assertEqual(response.context["profile"].username, self.user.username)

Особенности работы с базой данных

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