Мы уже написали много разных проектов на Python. Сегодня перейдём на новый уровень и разберёмся, как написать для них проверочные тесты.
Тесты — это программы, которые проверяют другую программу и отчитываются нам о результатах проверки. После них понятно, работает ли программа как задумано.
Писать тесты обычно долго и дорого, поэтому есть много разных инструментов для ускорения и упрощения этого процесса. Один из них — Pytest. Про него и поговорим.
Что такое Pytest
Pytest — свободно распространяемый популярный фреймворк для написания автотестов на Python. Это профессиональный функциональный инструмент, которым пользуются 7 000 компаний по всему миру в разных отраслях.
Без автотестов жизнь разработчиков была бы сильно сложнее. Программу можно тестировать самостоятельно: запускать код с разными входными данными и смотреть на результаты. Но такой способ работает только с небольшими проектами. Когда программа растёт, ручное тестирование становится слишком сложным и занимает много времени.
Поэтому при масштабировании кодовой базы нужен другой способ. Чтобы быть уверенным, что приложения и сервисы работают нормально и ничего не ломается при изменениях, разработчики добавляют в них программы-автотесты. Автотесты запускают основные фрагменты кода при разных условиях и проверяют, всё ли там в порядке.
Преимущества и недостатки Pytest
В Python уже есть встроенная библиотека для тестов, которая называется unittest, но Pytest отличается от неё и других инструментов тестирования. В основном в лучшую сторону, хотя минусы тоже есть.
Плюсы:
- Простота. Все тесты пишутся просто в виде функций, названия которых начинаются с
test_
. В unittest нужно использовать классы и методы. - Гибкость и расширяемость. Если каких-то возможностей не хватает, то можно подключить дополнительные плагины или написать свои.
- Читаемость. Тесты, написанные с Pytest, выглядят понятно для их автора и других разработчиков в команде.
- Мощные возможности. В Pytest проверки можно упрощать, настраивать и частично переиспользовать. Для этого можно пользоваться фикстурами, метками и параметризацией, про которые мы расскажем дальше.
- Удобство. Тесты легко запускаются одной командой, а если какая-то из проверок не прошла, Pytest показывает подробное сообщение с ошибкой.
Минусы:
- Изучение может быть сложным. Хотя сами тесты простые и удобные, но для понимания всех возможностей Pytest потребуется время.
- Несовместим с другими фреймворками. Другие технологии не умеют запускать тесты Pytest, хотя сам он может запускать тесты из остальных фреймворков.
Установка и настройка Pytest
Установить Pytest можно командой pip install -U pytest
.
Если используете виртуальную среду разработки IDE, эту команду нужно выполнить на вкладке терминала:
Как писать тесты с использованием Pytest
Настраивать Pytest перед использованием не нужно, можно сразу начинать писать тесты, но сначала следует запомнить основные правила.
Файлы с тестами должны иметь название, которое начинается с test_
или заканчивается на _test.py
:
test_math.py
user_test.py
Тестовые функции, то есть сами тесты, должны начинаться с test_
:
def test_sum():
assert 2 + 2 == 4
Assert — ключевое слово для проверки условия. Проверка используется в коде, чтобы убедиться, что код работает так, как ожидается. Если условие выполняется, программа идёт дальше. Если нет — выдаёт ошибку AssertionError
и останавливается.
Эта строка:
assert 2 + 2 == 4
проверяет условие, что 2 + 2 равняется 4. Такая проверка всегда будет заканчиваться успешно, поэтому программа всегда будет продолжать работать. Но если написать так:
x = 5
# всё нормально, программа идёт дальше
assert x > 0
# ошибка — x не меньше 0
assert x < 0
То результат будет зависеть от того, что находится в переменной x
.
Одна проверка — один тест. Можно сделать так, что одна тестовая функция будет проводить несколько проверок через assert
. Но так делать не надо. Если одна из этих проверок упадёт с ошибкой, то весь тест будет считаться как невыполненный.
# такие тесты писать не надо
def test_math():
assert 2 + 2 == 4
assert 2 * 3 == 6
assert 2 ** 3 == 8
Поэтому на каждую проверку нужен отдельный тест:
# правильный пример тестов:
# на одну проверку — один assert:
# тест 1
def test_sum():
assert 2 + 2 == 4
# тест 2
def test_multiply():
assert 2 * 3 == 6
# тест 3
def test_exponent():
assert 2 ** 3 == 8
Иногда от этого правила можно отойти, если в тесте обязательно проверить сразу несколько условий. Но всё равно удобнее видеть, какой именно тест не прошёл проверку.
Пишем тест-пример
Чтобы написать тест, нужен код для тестирования. Вот простой алгоритм, на основе которого создаются все тесты:
- Пишем Python-скрипт с программой, которую будем тестировать.
- Создаём тестовый файл, название которого начинается с
test_
или заканчивается на_test.py
. - Устанавливаем Pytest командой
pip install -U pytest
в терминале. - Импортируем скрипт в файл с тестами.
- Чтобы запустить тесты, вводим в консоли команду
pytest
.
Теперь на примере.
В файле main.py пишем программу с функцией сложения:
# пишем функцию сложения
def add(a, b):
return a + b
Создаём файл test_math.py и устанавливаем Pytest в проект. После этого импортируем в него наш файл с функцией и пишем тест, который проверяет результат сложения 2 и 3:
# test_main.py
# импортируем файл с функцией
import main
# пишем тест-функцию
def test_add():
# сохраняем в переменную результат сложения 2 и 3
result = main.add(2, 3)
# проверяем, что 2 + 3 действительно равно 5
assert result == 5
Всё готово к запуску, открываем терминал и пишем команду pytest
. Видим сообщение о старте тестовой сессии и успешно завершённых тестах:
Теперь попробуем поменять условие — будем проверять, что результат сложения равен 7, а не 5:
# пишем тест-функцию
def test_add():
# сохраняем в переменную результат сложения 2 и 3
result = main.add(2, 3)
# проверяем, что 2 + 3 действительно равно 5
assert result == 7
Получаем сообщение о том, что тест не пройден, и подробное описание ошибки:
Основные возможности Pytest
Кроме простых проверок условий, в Pytest можно использовать дополнительные возможности, которые делают работу удобнее и полезнее.
Фикстуры позволяют добавлять в тесты дополнительный код. Например, это полезно, если в нескольких тестах используются одни и те же повторяющиеся действия. Благодаря этому программа тестов становится проще и читаемее.
Метки дают возможность запускать только некоторые тесты. Это инструмент-фильтр.
Параметризация настраивает тесты так, что одну и ту же функцию можно проверить с разными входными данными. Без этого понадобится написать несколько однотипных тестов.
Теперь подробнее о каждой настройке.
Фикстуры
Технически фикстура — обёртка, в которую завёрнута тестовая функция. Это значит, что при запуске теста программа обязательно столкнётся с обёрткой и тем, что внутри, то есть запустит дополнительный код.Возьмём для примера встроенную функцию sum
, которая считает сумму, и передадим в неё список чисел. Список создадим в фикстуре:
# импортируем pytest
import pytest
# обозначаем функцию как фикстуру
@pytest.fixture
# пишем саму фикстуру
def sample_list():
# возвращаем из фикстуры список чисел от 1 до 5
return [1, 2, 3, 4, 5]
# пишем тестовую функцию и передаём в неё фикстуру
def test_sum(sample_list):
# проверяем, что сумма чисел, которые возвращает фикстура, равна 15
assert sum(sample_list) == 15
Теперь фикстуру, которая возвращает список, можно передавать в любую тестовую функцию. Вместо возвращения списка можно написать другой код — и его можно будет передать одной строкой вместо дублирования.
В итоге количество кода сокращается и повышается его читаемость.
Метки тестов
Метки позволяют группировать тесты и запускать только нужные. Например, можно пометить тесты как «медленные», «критические», «интеграционные» и запускать только те, которые нужны в определённый момент времени.Для примера создадим два простых теста и один пометим меткой slow
, а другой — меткой critical
:
# импортируем pytest
import pytest
# помечаем тестовую функцию меткой support
@pytest.mark.support
# создаём сам тест
def test_heavy_computation():
# имитируем сложную операцию:
# создаём последовательность чисел от 1
# до 999_999 и складываем все эти числа
write = sum(range(1_000_000))
# проверяем, что сумма положительная
assert write > 0
# помечаем тестовую функцию меткой makes
@pytest.mark.makes
# создаём сам тест
def test_login():
# проверяем, что в списке есть строка "admin"
assert "admin" in ["user", "admin", "guest", "easy"]
Теперь можно делать раздельные проверки.
Запускаем все тесты, кроме тех, которые помечены support
:
pytest -m "not support"
Результат в консоли — один запущенный тест:
Теперь сделаем по-другому. Запустим только те тесты, которые помечены как makes
:
pytest -m makes
Получаем один выполненный и пройденный тест:
Метки удобны, когда нужно управлять запуском большого количества разных тестов в больших проектах.
Параметризация
Чтобы не писать один и тот же тест с разными параметрами, можно создать одну функцию, а в качестве аргументов передать несколько наборов данных.
Как это выглядит:
- Перед тестовой функций нужно создать объект @pytest.mark.parametrize().
- В скобках параметризации сначала нужно передать строку с названиями аргументов. Например, "
a, b, expected
". - После названия аргументов нужно передать список, внутри которого будут храниться кортежи с разными значениями аргументов.
Смотрим на примере:
# импортируем pytest
import pytest
# задаём параметризацию: передаём строку аргументов в кавычках,
# список в квадратных скобках,
# кортежи значений в круглых скобках
@pytest.mark.parametrize("a, b, use", [
(1, 2, 3),
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
])
# создаём саму тестовую функцию
def test_add(a, b, use):
# проверяем, что сумма значений a и b равна use:
assert a + b == use
При выполнении этого кода Pytest автоматически создаст 4 теста, по количеству кортежей. Это заменяет 4 отдельных теста с assert
.
Кто этим занимается
В зависимости от целей автотесты на Pytest пишут разработчики, тестировщики и DevOps-инженеры.
- Разработчики проверяют логику кода, обработку ошибок или вообще пишут сначала тесты, а потом сам код. Это называется «разработка через тестирование».
- Тестировщики проверяют работу системы в разных сценариях и запускают тесты перед релизами каждой версии сервисов и приложений.
- DevOps-инженеры настраивают CI/CD-пайплайны для автоматического запуска тестов, следят, чтобы новый код не ломал уже существующую кодовую базу.
В следующий раз возьмём один из наших готовых проектов и напишем свои автотесты для его проверки.