Всем привет!
Продолжаем статью о знакомстве с тестированием в Python, которую мы подготовили для вас в рамках нашего курса «Разработчик Python».
Тестирование для Веб-Фреймворков Django и Flask
Если вы пишете тесты для веб-приложений, используя один из популярных фреймворков, например, Django или Flask, то стоит помнить о важных отличиях в написании и запуске таких тестов.
Чем Они Отличаются от Других Приложений
Подумайте о коде, который нужно протестировать в веб-приложении. Все маршруты, представления и модели требуют много импортов и знаний об используемом фреймворке.
Это похоже на тестирование автомобиля, о котором говорили в первой части туториала: перед тем, как провести простые тесты, вроде проверки работы фар, нужно включить компьютер в машине.
Django и Flask упрощают эту задачу и предоставляют тестовый фреймворк на базе unittest. Вы можете продолжать писать тесты привычным образом, но исполнять их чуть иначе.
Как пользоваться исполнителем тестов Django
Шаблон Django startapp создает файл tests.py в каталоге вашего приложения. Если его еще нет, создайте его со следующим содержимым:
Основное отличие от прошлых примеров — нужно наследовать из
Для исполнения тестового набора используйте
Если вам нужно несколько тестовых файлов, замените tests.py на папку tests, положите в нее пустой файл с названием
Больше информации доступно на сайте документации Django.
Как Пользоваться unittest и Flask
Для работы с Flask приложение необходимо импортировать и перевести в тестовый режим. Вы можете создать тестовый клиент и использовать его для отправки запросов к любым маршрутам в вашем приложении.
Инстанцирование тестового клиента происходит в методе setUp вашего тест-кейса. В следующем примере, my_app — название приложения. Не волнуйтесь, если не знаете, что делает setUp. Познакомимся с этим поближе в разделе «Более Продвинутые Сценарии Тестирования».
Код в тестовом файле будет выглядеть следующим образом:
Затем тест-кейсы можно выполнить с помощью команды
Больше информации доступно на сайте документации Flask.
Более Продвинутые Сценарии Тестирования
Перед тем как приступить к созданию тестов для своего приложения, запомните три основных этапа любого теста:
Это может быть сложнее, чем создание статического значения для исходных данных вроде строки или числа. Иногда ваше приложение требует инстанс класса или контекста. Что же делать в таком случае?
Данные, которые вы создаете в качестве исходных, называют фикстурой. Создание и повторное использование фикстур — распространенная практика.
Запуск одного и того же теста несколько раз с разными значениями в ожидании одного и того же результата называется параметризацией.
Обработка Ожидаемых Сбоев
Ранее, когда мы составляли список сценариев для тестирования
В таком случае ожидается, что
Есть определенный способ обработки ожидаемых ошибок. Можно использовать
Этот тест-кейс будет пройден, только если
Изоляция Поведений в Приложении
В прошлой части туториала, мы говорили о побочных эффектах. Они усложняют модульное тестирование, так как каждой запуск теста может выдавать разный результат или хуже — один тест может повлиять на состояние всего приложения и вызвать сбой другого теста!
Есть несколько простых методик для тестирования частей приложения с большим количеством побочных эффектов:
Написание Интеграционных Тестов
До сих пор мы уделяли больше внимания модульным тестам. Модульное тестирование — отличный способ создания предсказуемого и стабильного кода. Но, в конце концов, ваше приложение должно работать при запуске!
Интеграционное тестирование необходимо для проверки совместной работы нескольких компонентов приложения. Такое тестирование может потребовать отыгрывания роли покупателя или пользователя:
Все эти виды интеграционных тестов могут быть написаны так же, как и модульные, следуя шаблону Вводные Параметры, Выполнение, Утверждение. Наиболее существенным отличием является то, что интеграционные тесты одновременно проверяют больше компонентов, а значит приведут к большему количеству побочных эффектов, чем модульные тесты. Кроме того, интеграционные тесты требуют наличия большего количества фикстур, например, базы данных, сетевого сокета или файла конфигурации.
Поэтому рекомендуется разделять юнит-тесты и интеграционные тесты. Создание фикстур для интеграционных, например, тестовой базы данных или самих тест-кейсов, занимает гораздо больше времени, чем выполнение юнит-тестов, поэтому стоит проводить интеграционные тесты перед выходом в продакшн вместо их запуска при каждом коммите.
Простейший способ разделить модульные и интеграционные тесты — разнести их по разным папкам.
Выполнить определенную группу тестов можно разными способами. Флаг для уточнения директории источника, -s, может быть добавлен к unittest discover с путем, содержащим тесты:
unittest выдаст все результаты в директории tests/integration.
Тестирование Дата-Ориентированных Приложений
Многим интеграционным тестам требуются бэкенд-данные, например, база данных с определенными значениями. Представим, вам нужен тест для проверки правильности работы приложения с более чем 100 клиентами в базе данных, или проверки корректности отображения страницы заказа, даже если все названия товаров на японском.
Такие типы интеграционных тестов будут зависеть от различных тестовых фикстур, чтобы гарантировать их повторяемость и предсказуемость.
Тестовые данные стоит хранить в папке fixtures внутри директории интеграционных тестов, чтобы подчеркнуть их “тестовость”. Затем в тестах можно загрузить данные и запустить тест.
Вот пример структуры данных, состоящих из JSON файлов:
В тест-кейсе можно использовать метод .setUp() для загрузки тестовых данных из файла фикстуры по известному пути и выполнить несколько тестов с этими данными. Помните, что можно хранить несколько тест-кейсов в одном файле Python, unittest найдет и выполнит их. Можно иметь по одному тест-кейсу на каждый набор тестовых данных:
Если ваше приложение зависит от данных из удаленной локации, например удаленного API, убедитесь, что тесты повторяемы. Разработка может затянуться из-за тестов, провалившихся при отключении API и проблемах со связью. В таких случаях, лучше хранить удаленные фикстуры локально для повторного их вызова и отправки приложению.
В библиотеке
В следующей части будет про тестирование в нескольких окружениях и автоматизацию тестирования.
THE END
Комментарии\вопросы, как всегда приветствуются. Тут или заходите к Стасу на день открытых дверей.
Продолжаем статью о знакомстве с тестированием в Python, которую мы подготовили для вас в рамках нашего курса «Разработчик Python».
Тестирование для Веб-Фреймворков Django и Flask
Если вы пишете тесты для веб-приложений, используя один из популярных фреймворков, например, Django или Flask, то стоит помнить о важных отличиях в написании и запуске таких тестов.
Чем Они Отличаются от Других Приложений
Подумайте о коде, который нужно протестировать в веб-приложении. Все маршруты, представления и модели требуют много импортов и знаний об используемом фреймворке.
Это похоже на тестирование автомобиля, о котором говорили в первой части туториала: перед тем, как провести простые тесты, вроде проверки работы фар, нужно включить компьютер в машине.
Django и Flask упрощают эту задачу и предоставляют тестовый фреймворк на базе unittest. Вы можете продолжать писать тесты привычным образом, но исполнять их чуть иначе.
Как пользоваться исполнителем тестов Django
Шаблон Django startapp создает файл tests.py в каталоге вашего приложения. Если его еще нет, создайте его со следующим содержимым:
from django.test import TestCase
class MyTestCase(TestCase):
# Your test methods
Основное отличие от прошлых примеров — нужно наследовать из
django.test.TestCase
, а не unittest.TestCase
. API этих классов одинаковый, но класс Django TestCase настраивает все для тестирования.Для исполнения тестового набора используйте
manage.py
test вместо unittest в командной строке:$ python manage.py test
Если вам нужно несколько тестовых файлов, замените tests.py на папку tests, положите в нее пустой файл с названием
__init__.py
и создайте файлы test_*.py
. Django обнаружит их и выполнит.Больше информации доступно на сайте документации Django.
Как Пользоваться unittest и Flask
Для работы с Flask приложение необходимо импортировать и перевести в тестовый режим. Вы можете создать тестовый клиент и использовать его для отправки запросов к любым маршрутам в вашем приложении.
Инстанцирование тестового клиента происходит в методе setUp вашего тест-кейса. В следующем примере, my_app — название приложения. Не волнуйтесь, если не знаете, что делает setUp. Познакомимся с этим поближе в разделе «Более Продвинутые Сценарии Тестирования».
Код в тестовом файле будет выглядеть следующим образом:
import my_app
import unittest
class MyTestCase(unittest.TestCase):
def setUp(self):
my_app.app.testing = True
self.app = my_app.app.test_client()
def test_home(self):
result = self.app.get('/')
# Make your assertions
Затем тест-кейсы можно выполнить с помощью команды
python -m unittest discover.
Больше информации доступно на сайте документации Flask.
Более Продвинутые Сценарии Тестирования
Перед тем как приступить к созданию тестов для своего приложения, запомните три основных этапа любого теста:
- Создание входных параметров;
- Исполнение кода, получение данных вывода;
- Сравнение данных вывода с ожидаемым результатом;
Это может быть сложнее, чем создание статического значения для исходных данных вроде строки или числа. Иногда ваше приложение требует инстанс класса или контекста. Что же делать в таком случае?
Данные, которые вы создаете в качестве исходных, называют фикстурой. Создание и повторное использование фикстур — распространенная практика.
Запуск одного и того же теста несколько раз с разными значениями в ожидании одного и того же результата называется параметризацией.
Обработка Ожидаемых Сбоев
Ранее, когда мы составляли список сценариев для тестирования
sum()
, возник вопрос: что происходит, когда мы предоставляем плохое значение, например, одно целое число или строку?В таком случае ожидается, что
sum()
выдаст ошибку. При появлении ошибки тест провалится. Есть определенный способ обработки ожидаемых ошибок. Можно использовать
.assertRaises()
в качестве контекстного менеджера, а затем внутри блока with
выполнить тестовые шаги:import unittest
from my_sum import sum
class TestSum(unittest.TestCase):
def test_list_int(self):
"""
Тестируем, что удастся суммировать список целых чисел
"""
data = [1, 2, 3]
result = sum(data)
self.assertEqual(result, 6)
def test_list_fraction(self):
"""
Тестируем, что удастся суммировать список дробных чисел
"""
data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)]
result = sum(data)
self.assertEqual(result, 1)
def test_bad_type(self):
data = "banana"
with self.assertRaises(TypeError):
result = sum(data)
if __name__ == '__main__':
unittest.main()
Этот тест-кейс будет пройден, только если
sum(data)
выдаст TypeError. Вы можете заменить TypeError на любой другой тип исключений.Изоляция Поведений в Приложении
В прошлой части туториала, мы говорили о побочных эффектах. Они усложняют модульное тестирование, так как каждой запуск теста может выдавать разный результат или хуже — один тест может повлиять на состояние всего приложения и вызвать сбой другого теста!
Есть несколько простых методик для тестирования частей приложения с большим количеством побочных эффектов:
- Рефакторинг кода в соответствии с Принципом Единой Ответственности;
- Мокирование всех методов и вызовов функций для устранения побочных эффектов;
- Использование интеграционных тестов вместо модульных для этого фрагмента приложения.
- Если вы не знакомы с мокированием, посмотрите отличные примеры Python CLI Testing.
Написание Интеграционных Тестов
До сих пор мы уделяли больше внимания модульным тестам. Модульное тестирование — отличный способ создания предсказуемого и стабильного кода. Но, в конце концов, ваше приложение должно работать при запуске!
Интеграционное тестирование необходимо для проверки совместной работы нескольких компонентов приложения. Такое тестирование может потребовать отыгрывания роли покупателя или пользователя:
- Вызов HTTP REST API;
- Вызов Python API;
- Вызов веб-сервиса;
- Запуск командной строки.
Все эти виды интеграционных тестов могут быть написаны так же, как и модульные, следуя шаблону Вводные Параметры, Выполнение, Утверждение. Наиболее существенным отличием является то, что интеграционные тесты одновременно проверяют больше компонентов, а значит приведут к большему количеству побочных эффектов, чем модульные тесты. Кроме того, интеграционные тесты требуют наличия большего количества фикстур, например, базы данных, сетевого сокета или файла конфигурации.
Поэтому рекомендуется разделять юнит-тесты и интеграционные тесты. Создание фикстур для интеграционных, например, тестовой базы данных или самих тест-кейсов, занимает гораздо больше времени, чем выполнение юнит-тестов, поэтому стоит проводить интеграционные тесты перед выходом в продакшн вместо их запуска при каждом коммите.
Простейший способ разделить модульные и интеграционные тесты — разнести их по разным папкам.
project/
│
├── my_app/
│ └── __init__.py
│
└── tests/
|
├── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
├── __init__.py
└── test_integration.py
Выполнить определенную группу тестов можно разными способами. Флаг для уточнения директории источника, -s, может быть добавлен к unittest discover с путем, содержащим тесты:
$ python -m unittest discover -s tests/integration
unittest выдаст все результаты в директории tests/integration.
Тестирование Дата-Ориентированных Приложений
Многим интеграционным тестам требуются бэкенд-данные, например, база данных с определенными значениями. Представим, вам нужен тест для проверки правильности работы приложения с более чем 100 клиентами в базе данных, или проверки корректности отображения страницы заказа, даже если все названия товаров на японском.
Такие типы интеграционных тестов будут зависеть от различных тестовых фикстур, чтобы гарантировать их повторяемость и предсказуемость.
Тестовые данные стоит хранить в папке fixtures внутри директории интеграционных тестов, чтобы подчеркнуть их “тестовость”. Затем в тестах можно загрузить данные и запустить тест.
Вот пример структуры данных, состоящих из JSON файлов:
project/
│
├── my_app/
│ └── __init__.py
│
└── tests/
|
└── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
|
├── fixtures/
| ├── test_basic.json
| └── test_complex.json
|
├── __init__.py
└── test_integration.py
В тест-кейсе можно использовать метод .setUp() для загрузки тестовых данных из файла фикстуры по известному пути и выполнить несколько тестов с этими данными. Помните, что можно хранить несколько тест-кейсов в одном файле Python, unittest найдет и выполнит их. Можно иметь по одному тест-кейсу на каждый набор тестовых данных:
import unittest
class TestBasic(unittest.TestCase):
def setUp(self):
# Load test data
self.app = App(database='fixtures/test_basic.json')
def test_customer_count(self):
self.assertEqual(len(self.app.customers), 100)
def test_existence_of_customer(self):
customer = self.app.get_customer(id=10)
self.assertEqual(customer.name, "Org XYZ")
self.assertEqual(customer.address, "10 Red Road, Reading")
class TestComplexData(unittest.TestCase):
def setUp(self):
# load test data
self.app = App(database='fixtures/test_complex.json')
def test_customer_count(self):
self.assertEqual(len(self.app.customers), 10000)
def test_existence_of_customer(self):
customer = self.app.get_customer(id=9999)
self.assertEqual(customer.name, u"バナナ")
self.assertEqual(customer.address, "10 Red Road, Akihabara, Tokyo")
if __name__ == '__main__':
unittest.main()
Если ваше приложение зависит от данных из удаленной локации, например удаленного API, убедитесь, что тесты повторяемы. Разработка может затянуться из-за тестов, провалившихся при отключении API и проблемах со связью. В таких случаях, лучше хранить удаленные фикстуры локально для повторного их вызова и отправки приложению.
В библиотеке
requests
есть бесплатный пакет responses, позволяющий создавать ответные фикстуры и сохранять их в тестовых папках. Узнайте больше на их странице GitHub.В следующей части будет про тестирование в нескольких окружениях и автоматизацию тестирования.
THE END
Комментарии\вопросы, как всегда приветствуются. Тут или заходите к Стасу на день открытых дверей.