HTML Diff
1 added 2 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Тестирование приложений на Django - неотъемлемая часть профессиональной жизни веб-разработчиков на Python. Сюда входит написание различных тестов:</p>
1 <p>Тестирование приложений на Django - неотъемлемая часть профессиональной жизни веб-разработчиков на Python. Сюда входит написание различных тестов:</p>
2 <ul><li>Юнит-тестов для отдельных модулей</li>
2 <ul><li>Юнит-тестов для отдельных модулей</li>
3 <li>Интеграционных тестов, проверяющих работоспособность всего приложения</li>
3 <li>Интеграционных тестов, проверяющих работоспособность всего приложения</li>
4 </ul><p>В этом уроке мы научимся создавать интеграционные тесты для наших веб-приложений на Django.</p>
4 </ul><p>В этом уроке мы научимся создавать интеграционные тесты для наших веб-приложений на Django.</p>
5 <p>Веб-приложения работают по сети, обрабатывая HTTP-запросы. Такое поведение придется повторять прямо в тестах или как-то имитировать. Django позволяет использовать оба подхода. Мы остановимся на подходе с подменой веб-сервера, чтобы ускорить запуск и выполнение тестов. В остальном эти тесты проверяют работу приложения от запроса до ответа, что дает очень высокую степень уверенности в том, что приложение работает.</p>
5 <p>Веб-приложения работают по сети, обрабатывая HTTP-запросы. Такое поведение придется повторять прямо в тестах или как-то имитировать. Django позволяет использовать оба подхода. Мы остановимся на подходе с подменой веб-сервера, чтобы ускорить запуск и выполнение тестов. В остальном эти тесты проверяют работу приложения от запроса до ответа, что дает очень высокую степень уверенности в том, что приложение работает.</p>
6 <h2>Первый тест</h2>
6 <h2>Первый тест</h2>
7 - <p>Интеграционные тесты в Django связаны с маршрутами. Каждй тест - это запрос на конкретный адрес для тестирования конкретного маршрута. Количество тестов для одного маршрута может быть разным, но конкретный тест - это всегда запрос-ответ.</p>
7 + <p>Интеграционные тесты в Django связаны с маршрутами. Каждый тест - это запрос на конкретный адрес для тестирования конкретного маршрута. Количество тестов для одного маршрута может быть разным, но конкретный тест - это всегда запрос-ответ.</p>
8 <p>Начнем с примера. Предположим, что у нас есть маршрут<em>/users/</em>, который возвращает список пользователей. Тест на такой маршрут должен выполнить запрос на этот адрес. Вот как будет выглядеть структура файлов в этом случае:</p>
8 <p>Начнем с примера. Предположим, что у нас есть маршрут<em>/users/</em>, который возвращает список пользователей. Тест на такой маршрут должен выполнить запрос на этот адрес. Вот как будет выглядеть структура файлов в этом случае:</p>
9 - <p>. ├── manage.py ├── pyproject.toml └── simple_blog ├── users │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py</p>
 
10 <p>Тесты расположены в директории приложения<em>simple_blog/users/</em>.</p>
9 <p>Тесты расположены в директории приложения<em>simple_blog/users/</em>.</p>
11 <p>Сам тест выглядит так:</p>
10 <p>Сам тест выглядит так:</p>
12 <p>Файл тестов - это класс фреймворка unittest, в котором тестовые методы начинаются с test_. Во время теста Django делает запрос через специальный объект client. Разберем по шагам:</p>
11 <p>Файл тестов - это класс фреймворка unittest, в котором тестовые методы начинаются с test_. Во время теста Django делает запрос через специальный объект client. Разберем по шагам:</p>
13 <ul><li>Метод get(reverse("users:index")) формирует объект запроса к указанной странице. Кроме GET запроса, мы можем выполнить любой другой запрос. На самом деле здесь не происходит HTTP-вызова - запрос передается в приложение напрямую, поэтому тесты работают быстрее, чем с реальным веб-сервером</li>
12 <ul><li>Метод get(reverse("users:index")) формирует объект запроса к указанной странице. Кроме GET запроса, мы можем выполнить любой другой запрос. На самом деле здесь не происходит HTTP-вызова - запрос передается в приложение напрямую, поэтому тесты работают быстрее, чем с реальным веб-сервером</li>
14 <li>Метод assertEqual(response.status_code, 200) проверяет, что вернулся ответ 200. По необходимости можно проверить любой другой статус</li>
13 <li>Метод assertEqual(response.status_code, 200) проверяет, что вернулся ответ 200. По необходимости можно проверить любой другой статус</li>
15 </ul><p>Проверка на код ответа считается одной из базовых проверок. Она показывает, что приложение в целом отработало ожидаемо. При этом мы не можем с уверенностью сказать, что все правильно.</p>
14 </ul><p>Проверка на код ответа считается одной из базовых проверок. Она показывает, что приложение в целом отработало ожидаемо. При этом мы не можем с уверенностью сказать, что все правильно.</p>
16 <p>Например, мы ожидаем, что в теле ответа будет HTML определенной структуры с нужными данными, но вдруг там ничего нет? Для контроля ответа нужно добавить проверку тела ответа.</p>
15 <p>Например, мы ожидаем, что в теле ответа будет HTML определенной структуры с нужными данными, но вдруг там ничего нет? Для контроля ответа нужно добавить проверку тела ответа.</p>
17 <p>И последний шаг - запуск тестов:</p>
16 <p>И последний шаг - запуск тестов:</p>
18 <h2>Взаимодействие с базой</h2>
17 <h2>Взаимодействие с базой</h2>
19 <p>Пример теста списка пользователей не включает в себя одну важную деталь - наполнение базы данных. По умолчанию тесты используют ту базу данных, которая указана в конфигурации. За ее наполнение отвечает программист. Кроме наполнения базы, нам нужна еще и ее очистка.</p>
18 <p>Пример теста списка пользователей не включает в себя одну важную деталь - наполнение базы данных. По умолчанию тесты используют ту базу данных, которая указана в конфигурации. За ее наполнение отвечает программист. Кроме наполнения базы, нам нужна еще и ее очистка.</p>
20 <p>Представьте, что мы написали тест, который создает пользователя. Если после теста мы не удалим этого пользователя, то следующий тест может завершиться с ошибкой - он не рассчитывает, что в базе уже есть такие данные. По этой причине в Django каждый тест выполняется в отдельной транзакции, которая откатывается в конце теста. Таким образом достигается полная изоляция тестов друг от друга.</p>
19 <p>Представьте, что мы написали тест, который создает пользователя. Если после теста мы не удалим этого пользователя, то следующий тест может завершиться с ошибкой - он не рассчитывает, что в базе уже есть такие данные. По этой причине в Django каждый тест выполняется в отдельной транзакции, которая откатывается в конце теста. Таким образом достигается полная изоляция тестов друг от друга.</p>
21 <p>Посмотрим на работу такого теста на примере запроса, обновляющего пользователя. Для этой операции используем маршрут /users/&lt;pk&gt;. Для выполнения запроса нам понадобится идентификатор пользователя.</p>
20 <p>Посмотрим на работу такого теста на примере запроса, обновляющего пользователя. Для этой операции используем маршрут /users/&lt;pk&gt;. Для выполнения запроса нам понадобится идентификатор пользователя.</p>
22 <p><strong>Шаг 1.</strong>Сначала мы создаем пользователя. Для наполнения базы данными используется метод setUp().</p>
21 <p><strong>Шаг 1.</strong>Сначала мы создаем пользователя. Для наполнения базы данными используется метод setUp().</p>
23 <p><strong>Шаг 2.</strong>Затем мы подготавливаем запрос. В самом запросе формируем правильный адрес, подставляя идентификатор созданного пользователя:</p>
22 <p><strong>Шаг 2.</strong>Затем мы подготавливаем запрос. В самом запросе формируем правильный адрес, подставляя идентификатор созданного пользователя:</p>
24 <p><strong>Шаг 3.</strong>Выполняем запрос и проверяем, что он действительно изменил пользователя в базе данных:</p>
23 <p><strong>Шаг 3.</strong>Выполняем запрос и проверяем, что он действительно изменил пользователя в базе данных:</p>
25 <p>Обычно в простых сценариях взаимодействия все наполнение базы можно уместить в setUp(). Но для ситуаций, когда требуется больше данных, лучше воспользоваться Django-фикстурами. Фикстура здесь это набор данных, чаще всего в формате JSON. Самым простым способом создать фикстуру будет сделать дамп, выгрузку данных, базы. Перед этим, разумеется нужно создать какие-то записи в базе.</p>
24 <p>Обычно в простых сценариях взаимодействия все наполнение базы можно уместить в setUp(). Но для ситуаций, когда требуется больше данных, лучше воспользоваться Django-фикстурами. Фикстура здесь это набор данных, чаще всего в формате JSON. Самым простым способом создать фикстуру будет сделать дамп, выгрузку данных, базы. Перед этим, разумеется нужно создать какие-то записи в базе.</p>
26 <p>В результате Django сгенерирует JSON файл, который мы можем уже редактировать. Ниже пример фикстуры users.json в одном из наших проектов.</p>
25 <p>В результате Django сгенерирует JSON файл, который мы можем уже редактировать. Ниже пример фикстуры users.json в одном из наших проектов.</p>
27 <p>После чего фикстуру нужно сохранить в директории приложения<em>fixtures</em>и уже можем использовать ее в тестах. Django самостоятельно перед каждым тестом, еще до выполнения setUp(), загрузит данные из фикстуры в тестовую базу.</p>
26 <p>После чего фикстуру нужно сохранить в директории приложения<em>fixtures</em>и уже можем использовать ее в тестах. Django самостоятельно перед каждым тестом, еще до выполнения setUp(), загрузит данные из фикстуры в тестовую базу.</p>
28 <p>Обратите внимание на важную деталь, связанную с интеграционными тестами. На протяжении урока мы писали тесты и убеждались, что приложение работает, даже не посмотрев на реализацию самого приложения. В этом и заключается суть интеграционных тестов. Нам не важно, как написано приложение внутри - мы убеждаемся только в том, что оно работает правильно. Из-за этого интеграционные тесты очень устойчивы к изменениям в коде, они меняются в основном из-за изменений API.</p>
27 <p>Обратите внимание на важную деталь, связанную с интеграционными тестами. На протяжении урока мы писали тесты и убеждались, что приложение работает, даже не посмотрев на реализацию самого приложения. В этом и заключается суть интеграционных тестов. Нам не важно, как написано приложение внутри - мы убеждаемся только в том, что оно работает правильно. Из-за этого интеграционные тесты очень устойчивы к изменениям в коде, они меняются в основном из-за изменений API.</p>
29 <h2>Выводы</h2>
28 <h2>Выводы</h2>
30 <p>Мы научились использовать встроенный механизм тестирования для написания тестов на Django. Django-тесты обладают важными преимуществами как бесшовная работа с базой данных, и абстракция браузера. С помощью их сочетания мы можем писать легко тесты проверяющие функционал больших частей приложения.</p>
29 <p>Мы научились использовать встроенный механизм тестирования для написания тестов на Django. Django-тесты обладают важными преимуществами как бесшовная работа с базой данных, и абстракция браузера. С помощью их сочетания мы можем писать легко тесты проверяющие функционал больших частей приложения.</p>