Основы pytest: Пишем первые тесты на Python
В предыдущей статье мы разобрались, что такое тестирование, зачем оно нужно и какие бывают типы тестов. Теперь пришло время перейти к практике и познакомиться с одним из самых популярных фреймворков для тестирования в Python — pytest.
Почему именно pytest?
pytest завоевал популярность благодаря нескольким ключевым преимуществам, особенно важным для начинающих:
- Простой синтаксис: Для написания тестов используются стандартные функции Python и обычные assert утверждения. Это делает код тестов чище и понятнее по сравнению с более многословными xUnit-фреймворками.
- Автоматическое обнаружение тестов: pytest автоматически находит тестовые файлы (по умолчанию test_*.py или *_test.py) и тестовые функции (по умолчанию test_*) без необходимости явной регистрации.
- Информативный вывод: При сбоях тестов pytest предоставляет подробную информацию, помогающую быстро найти причину проблемы.
- Мощная экосистема: Существует множество плагинов, расширяющих функциональность pytest (например, для работы с веб-фреймворками, генерации отчетов о покрытии и т.д.).
- Низкий порог вхождения: Легко начать писать простые тесты, постепенно осваивая более продвинутые возможности.
Начало работы с pytest
Установка
Если у вас еще не установлен pytest, вы можете сделать это с помощью pip:
pip install pytest
Ваш первый тест
Создайте файл с именем test_example.py. pytest автоматически распознает его как файл с тестами благодаря префиксу test_.
# test_example.py def add(x, y): return x + y def test_add_positive_numbers(): assert add(1, 2) == 3 def test_add_negative_numbers(): assert add(-1, -2) == -3 def test_add_mixed_numbers(): assert add(5, -2) == 3
В этом примере:
- Мы определили простую функцию add(), которую хотим протестировать.
- Мы написали три тестовые функции: test_add_positive_numbers(), test_add_negative_numbers() и test_add_mixed_numbers(). Каждая из них проверяет один конкретный аспект работы функции add().
- Внутри каждой тестовой функции мы используем ключевое слово assert, за которым следует выражение. Если выражение истинно, тест считается пройденным. Если ложно — тест провален.
Запуск тестов
Откройте терминал, перейдите в директорию, где вы сохранили файл test_example.py, и выполните команду:
pytest
pytest найдет все файлы и функции, соответствующие его соглашениям об именовании, и запустит их. Вы должны увидеть примерно такой вывод:
============================= test session starts ============================== platform ... -- Python ... plugins: ... collected 3 items test_example.py ... [100%] ============================== 3 passed in 0.XXs ===============================
Точка . для каждого теста означает, что он прошел успешно.
Для более подробного вывода используйте флаг -v (verbose):
pytest -v
Вывод будет содержать имена выполненных тестов:
============================= test session starts ============================== platform ... -- Python ... plugins: ... collected 3 items test_example.py::test_add_positive_numbers PASSED [ 33%] test_example.py::test_add_negative_numbers PASSED [ 66%] test_example.py::test_add_mixed_numbers PASSED [100%] ============================== 3 passed in 0.XXs ===============================
Вы также можете указать pytest запустить тесты только из определенного файла:
pytest test_example.py -v
Понимание вывода pytest: успешные и неудачные тесты
Важно уметь читать вывод pytest, особенно когда тесты падают.
Успешный тест
Как мы видели, успешные тесты помечаются точкой . (в обычном режиме) или словом PASSED (в режиме -v).
Неудачный тест
Давайте намеренно испортим один из наших тестов в файле test_example.py:
# test_example.py (с ошибкой) def add(x, y): return x + y def test_add_positive_numbers(): assert add(1, 2) == 3 def test_add_negative_numbers(): assert add(-1, -2) == -3 def test_add_mixed_numbers_failed(): # Изменим имя для наглядности и сделаем ошибку assert add(5, -2) == 10 # Ожидаем 3, но написали 10
Теперь запустим pytest -v:
============================= test session starts ============================== collected 3 items test_example.py::test_add_positive_numbers PASSED [ 33%] test_example.py::test_add_negative_numbers PASSED [ 66%] test_example.py::test_add_mixed_numbers_failed FAILED [100%] =================================== FAILURES =================================== ______________________ test_add_mixed_numbers_failed _______________________ def test_add_mixed_numbers_failed(): # Изменим имя для наглядности и сделаем ошибку > assert add(5, -2) == 10 # Ожидаем 3, но написали 10 E assert 3 == 10 E + where 3 = add(5, -2) test_example.py:14: AssertionError =========================== short test summary info ============================ FAILED test_example.py::test_add_mixed_numbers_failed - assert 3 == 10 ========================= 1 failed, 2 passed in 0.XXs ==========================
Что мы видим в выводе для упавшего теста:
- FAILED: Статус теста.
- FAILURES Section: Подробная информация о каждом упавшем тесте.
- Traceback: Путь к строке в коде, где произошла ошибка (test_example.py:14: AssertionError).
- E assert 3 == 10: pytest показывает, что именно пошло не так. Он вычислил add(5, -2) как 3 и сравнил это с 10. Поскольку 3 == 10 ложно, assert провалился.
- short test summary info: Краткая сводка о количестве упавших и прошедших тестов.
Этот подробный вывод очень помогает в отладке.
Организация тестов
По мере роста вашего проекта количество тестов будет увеличиваться. pytest предлагает несколько способов их организации.
Тестовые файлы
Как уже упоминалось, pytest автоматически находит файлы, имена которых начинаются с test_ или заканчиваются на _test.py.
Тестовые функции
Внутри этих файлов pytest ищет функции, имена которых начинаются с test_.
Группировка тестов в классы
Для лучшей организации вы можете группировать связанные тесты в классы. Имена таких классов должны начинаться с Test.
# test_calculator.py class Calculator: def add(self, x, y): return x + y def subtract(self, x, y): return x - y # Группируем тесты для калькулятора в класс class TestCalculator: def test_add(self): calc = Calculator() assert calc.add(2, 3) == 5 assert calc.add(-1, 1) == 0 def test_subtract(self): calc = Calculator() assert calc.subtract(5, 3) == 2 assert calc.subtract(2, 5) == -3 # Можно также иметь тесты вне класса в том же файле def test_outside_class(): assert True
Обратите внимание, что этим классам не нужно наследоваться от какого-либо специального базового класса (в отличие от unittest.TestCase, который мы рассмотрим позже). pytest их обнаружит и выполнит все методы test_* внутри.
При запуске pytest -v вы увидите:
test_calculator.py::TestCalculator::test_add PASSED test_calculator.py::TestCalculator::test_subtract PASSED test_calculator.py::test_outside_class PASSED
Что дальше?
Мы рассмотрели самые основы pytest: как писать простые тесты, запускать их и интерпретировать результаты. Это уже мощный инструмент для проверки корректности вашего кода.
В следующей статье мы углубимся в более продвинутые и полезные возможности pytest, такие как фикстуры (для управления состоянием тестов) и параметризация (для запуска одного теста с разными данными).
Какое утверждение об основах работы с pytest является верным?