Файлы
practice
migrations
__init__.py
templates
index.html
models.py
__init__.py
apps.py
context_processors.py
admin.py
tests.py
urls.py
views.py
task5views
__init__.py
settings.py
urls.py
wsgi.py
manage.py
tests.py
Урок 3: Подготовка условий для запуска тестов
Напишите тесты для проверки страницы сайта с тарифными планами.
Проверьте, что:
  • главная страница доступна неавторизованному пользователю, а раздел администратора — нет
  • переменная plans есть в контексте шаблона
  • имя шаблона, который вызывается при рендеринге главной страницы — index.html
  • тип переменной plans — это список, состоящий из 3-х элементов, а их тип — словарь
  • на результирующей странице показываются названия тарифных планов и подставляется правильная тема (subject) в ссылку на кнопке "Связаться"
  • в контекстных переменных шаблона присутствует текущий год и он же правильно появляется на странице
Обратите внимание: это задание творческое, и мы не будем автоматически проверять, насколько хороши ваши тесты. Проверку пройдут любые корректные тесты, так что не спешите нажимать кнопку «Проверить». Поэкспериментируйте вдоволь и только после этого идите дальше.
Браузер751x939
Браузер751x939

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

Перед выполнением тестов иногда нужно подготовить определённые условия для их запуска: создать пользователя с определённым уровнем доступа или эмулировать работу браузера, чтобы хранить информацию о пользовательской сессии.
Подготовка контекста применяется в различных системах тестирования и традиционно называется 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.
Проверить работу этого кода можно
  • заменив в коде логин и пароль на данные любого зарегистрированного в вашем проекте пользователя
  • или предварительно создав пользователя с логином 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() # — Браузер, сделай 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-объект. В нём содержится ответ сервера и некоторые дополнительные свойства:
  • client — объект клиента, который использовался для обращения
  • content — данные ответа в виде строки байтов
  • context — словарь переменных, переданный для отрисовки шаблона при вызове функции render()
  • request — объект request, первый параметр view-функций
  • templates — перечень объектов шаблонов, вызванных для отрисовки страницы, возвращаемой сервером
  • resolver_match — специальный объект, соответствующий объекту path() из списка urlpatterns
Пример теста, который использует возможности клиента и ответов сервера:
Скопировать код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 создает временную версию базы в памяти, а не обращается к существующей базе. Все записи и изменения, которые вносятся в базу во время тестов, производятся именно с этой временной версией, а основная база проекта остаётся в исходном состоянии.
При обычной работе в интерактивном режиме вы обращаетесь к базе реального проекта, а во время запуска тестов — ко временной базе.
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Удалить файл
Переименовать
Новый файл
Новая папка
Удалить папку
Переименовать
Удалить файл
Новый файл
Новая папка