Основы pytest: Пишем первые тесты на Python

В предыдущей статье мы разобрались, что такое тестирование, зачем оно нужно и какие бывают типы тестов. Теперь пришло время перейти к практике и познакомиться с одним из самых популярных фреймворков для тестирования в Python — pytest.

Почему именно pytest?

pytest завоевал популярность благодаря нескольким ключевым преимуществам, особенно важным для начинающих:

  • Простой синтаксис: Для написания тестов используются стандартные функции Python и обычные assert утверждения. Это делает код тестов чище и понятнее по сравнению с более многословными xUnit-фреймворками.
  • Автоматическое обнаружение тестов: pytest автоматически находит тестовые файлы (по умолчанию test_*.py или *_test.py) и тестовые функции (по умолчанию test_*) без необходимости явной регистрации.
  • Информативный вывод: При сбоях тестов pytest предоставляет подробную информацию, помогающую быстро найти причину проблемы.
  • Мощная экосистема: Существует множество плагинов, расширяющих функциональность pytest (например, для работы с веб-фреймворками, генерации отчетов о покрытии и т.д.).
  • Низкий порог вхождения: Легко начать писать простые тесты, постепенно осваивая более продвинутые возможности.

Начало работы с pytest

Установка

Если у вас еще не установлен pytest, вы можете сделать это с помощью pip:

Python 3.13
pip install pytest

Ваш первый тест

Создайте файл с именем test_example.py. pytest автоматически распознает его как файл с тестами благодаря префиксу test_.

Python 3.13
# 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, и выполните команду:

Python 3.13
pytest

pytest найдет все файлы и функции, соответствующие его соглашениям об именовании, и запустит их. Вы должны увидеть примерно такой вывод:

Python 3.13
============================= test session starts ==============================
platform ... -- Python ...
plugins: ...
collected 3 items

test_example.py ...                                                      [100%]

============================== 3 passed in 0.XXs ===============================

Точка . для каждого теста означает, что он прошел успешно.

Для более подробного вывода используйте флаг -v (verbose):

Python 3.13
pytest -v

Вывод будет содержать имена выполненных тестов:

Python 3.13
============================= 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 запустить тесты только из определенного файла:

Python 3.13
pytest test_example.py -v

Понимание вывода pytest: успешные и неудачные тесты

Важно уметь читать вывод pytest, особенно когда тесты падают.

Успешный тест

Как мы видели, успешные тесты помечаются точкой . (в обычном режиме) или словом PASSED (в режиме -v).

Неудачный тест

Давайте намеренно испортим один из наших тестов в файле test_example.py:

Python 3.13
# 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:

Python 3.13
============================= 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 ==========================

Что мы видим в выводе для упавшего теста:

  1. FAILED: Статус теста.
  2. FAILURES Section: Подробная информация о каждом упавшем тесте.
  3. Traceback: Путь к строке в коде, где произошла ошибка (test_example.py:14: AssertionError).
  4. E assert 3 == 10: pytest показывает, что именно пошло не так. Он вычислил add(5, -2) как 3 и сравнил это с 10. Поскольку 3 == 10 ложно, assert провалился.
  5. short test summary info: Краткая сводка о количестве упавших и прошедших тестов.

Этот подробный вывод очень помогает в отладке.

Организация тестов

По мере роста вашего проекта количество тестов будет увеличиваться. pytest предлагает несколько способов их организации.

Тестовые файлы

Как уже упоминалось, pytest автоматически находит файлы, имена которых начинаются с test_ или заканчиваются на _test.py.

Тестовые функции

Внутри этих файлов pytest ищет функции, имена которых начинаются с test_.

Группировка тестов в классы

Для лучшей организации вы можете группировать связанные тесты в классы. Имена таких классов должны начинаться с Test.

Python 3.13
# 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 вы увидите:

Python 3.13
test_calculator.py::TestCalculator::test_add PASSED
test_calculator.py::TestCalculator::test_subtract PASSED
test_calculator.py::test_outside_class PASSED

Что дальше?

Мы рассмотрели самые основы pytest: как писать простые тесты, запускать их и интерпретировать результаты. Это уже мощный инструмент для проверки корректности вашего кода.

В следующей статье мы углубимся в более продвинутые и полезные возможности pytest, такие как фикстуры (для управления состоянием тестов) и параметризация (для запуска одного теста с разными данными).


Какое утверждение об основах работы с pytest является верным?


Мы с вами на связи
Русский