Акции и промокоды Отзывы о школах

Зачем применить Pytest даже в простом проекте

#Блог

Pytest — это мощный фреймворк для тестирования Python-кода, который по праву заслужил звание самого популярного инструмента в своей категории. По сути, это ваш верный цифровой санитар, который проверяет, не заболел ли ваш код какой-нибудь непредвиденной ошибкой.

Преимущества:

  • Простота — тесты пишутся как обычные функции, а не громоздкие классы.
  • Гибкость — расширяется плагинами, которых больше тысячи.
  • Читаемость — тесты понятны даже новичкам в команде.
  • Мощьфикстуры, метки и параметризация для продвинутых случаев.
  • Удобство — подробные отчеты об ошибках, запуск одной командой.

Недостатки:

  • Сложная кривая обучения для продвинутых возможностей.
  • Несовместимость с другими фреймворками (в одну сторону).
  • Не входит в стандартную библиотеку Python.

Теперь давайте разберемся, почему вообще стоит тестировать код, будто у нас нет других занятий.

Почему тестирование кода важно

Давайте признаем очевидное — каждый из нас верит, что пишет идеальный код прямо с клавиатуры. Но потом наступает понедельник, и мы обнаруживаем, что наш «безупречный» алгоритм почему-то отправляет пользователям фотографии котиков вместо банковских выписок.

Тестирование — это как страховка от вашего собственного оптимизма. Подумайте о нем как о дотошном бухгалтере, который проверяет каждую цифру в отчете, пока вы мечтательно смотрите в окно.

В мире разработки существует несколько типов тестов:

  • Модульные тесты — проверяют отдельные компоненты (функции, классы) в изоляции. Это как протестировать работу тормозов автомобиля, не садясь за руль.
  • Функциональные тесты — оценивают, насколько программа соответствует бизнес-требованиям. Другими словами, делает ли ваш код то, что нужно, или просто то, что вы смогли заставить его делать.
  • Интеграционные тесты — проверяют, как компоненты работают вместе. Примерно как поставить двух интровертов в одну комнату и посмотреть, сумеют ли они договориться.
относительный вес каждого типа тестов

Круговая диаграмма с секторами «Модульные», «Функциональные», «Интеграционные» тесты (40 / 30 / 30 %). Показывает относительный вес каждого типа тестов и подчёркивает, что модульные являются наиболее распространёнными.

А теперь взглянем, как Pytest выглядит на фоне конкурентов:

Характеристика Pytest Unittest Doctest
Синтаксис Лаконичный Многословный (классы и методы) Встроен в документацию
Простота написания Высокая Средняя Средняя
Гибкость Очень высокая Средняя Низкая
Расширяемость 1000+ плагинов Ограниченная Минимальная
Читаемость отчетов Отличная Хорошая Базовая
Входит в стандартную библиотеку Нет Да Да
Умеет запускать тесты других фреймворков Да Нет Нет
в чём Pytest превосходит альтернативы

Горизонтальная столбчатая диаграмма по четыре метрики («Синтаксис», «Гибкость», «Расширяемость», «Читаемость отчётов») для каждого фреймворка (Pytest, Unittest, Doctest). Помогает визуально оценить, в чём Pytest превосходит альтернативы.

Установка и настройка

Установка Pytest — это проще, чем настроить будильник. Главное — не проспать после этого сроки проекта.

Начнем с создания виртуального окружения, чтобы ваши тесты не устроили междоусобицу с версиями библиотек в других проектах. Я знаю, многие пропускают этот шаг, как утреннюю зарядку, но поверьте — это экономит нервы в долгосрочной перспективе.

$ mkdir python-tests

$ cd python-tests

$ python3 -m venv .venv

$ source .venv/bin/activate  # На Windows: .venv\Scripts\activate.bat

Теперь можно устанавливать сам Pytest. Делается это одной волшебной командой:

$ pip install -U pytest

Флаг -U здесь означает «обновить, если уже установлено». Это как выбрать опцию «возьми последнюю модель» при покупке iPhone, даже если у вас уже есть смартфон.

Проверить, что установка прошла успешно, можно с помощью команды:

$ pytest --version

Если в ответ вы увидите что-то вроде номера версии, а не ругательств pip о несуществующих пакетах — поздравляю, вы в клубе! Теперь можно переходить к сочинению тестов, которые будут следить за здоровьем вашего кода не хуже, чем фитнес-трекер за вашими шагами.

Основы написания тестов с Pytest

Основные правила организации тестов

Если вы когда-нибудь задумывались, как бы упростить своё существование, то Pytest даёт совершенно конкретный ответ на этот философский вопрос: нужно всего лишь следовать нескольким простым правилам именования, и ваша жизнь тестировщика станет значительно менее болезненной.

Первое правило тестового клуба Pytest — название файла должно начинаться с test_ или заканчиваться на _test.py. Например, test_user_login.py или payment_system_test.py. И нет, файл i_really_test_this_stuff.py не подойдёт, как бы вам ни хотелось проявить оригинальность.

Второе правило — названия тестовых функций должны начинаться с test_. Например:

def test_user_can_login():

    # ваш код здесь

def test_payment_processed():

    # и здесь тоже

А вот функция verify_login() останется незамеченной Pytest, как человек в камуфляжной одежде в лесу.

Ключевое слово в тестах Pytest — assert. Это ваш цифровой судья, который определяет, пройден тест или нет. Если условие после assert истинно — тест считается пройденным, если ложно — провалившимся. Никаких громоздких конструкций типа self.assertEqual(), self.assertTrue() и прочих излишеств, к которым нас приучил unittest.

Вот простой пример теста:

def test_addition_works_correctly():

    result = 2 + 2

    assert result == 4, "Математика перестала работать, мир обречён"

Текст после запятой — это сообщение, которое будет выведено при провале теста. Считайте это предсмертной запиской вашего кода.

Запуск тестов в Pytest

Чтобы запустить все тесты в текущей директории и поддиректориях, используйте команду:

$ pytest

Если вы не хотите запускать все тесты (а вдруг там затаился тот, который идёт 3 часа?), можно указать конкретный файл:

$ pytest test_login.py

Или даже конкретную функцию:

$ pytest test_login.py::test_admin_login

Pytest предлагает разные уровни подробности отчётов. По умолчанию он показывает только проваленные тесты, но вы можете попросить его быть более разговорчивым с помощью флага -v (verbose):

$ pytest -v

Или наоборот, заставить его говорить только о самом важном:

$ pytest -q

Флаг -q (quiet) полезен, когда тестов много, и вы не хотите, чтобы весь терминал заполнился точками успешных тестов, как звёздное небо.

Расширенные возможности Pytest

Фикстуры: что это и как работают?

Фикстуры в Pytest — это что-то вроде швейцарского ножа для тестировщика, только без риска порезаться. По сути, это функции, которые выполняются до (или после) тестов, чтобы настроить окружение, подготовить данные или выполнить другие подготовительные действия, которые в противном случае пришлось бы копировать в каждый тест.

Представьте, что у вас есть десяток тестов, и каждый требует подключения к базе данных. Без фикстур вам пришлось бы писать один и тот же код подключения в каждом тесте — а это примерно так же эффективно, как печатать одно и то же сообщение десяти разным людям вместо использования групповой рассылки.

Вот как выглядит простая фикстура:

import pytest

@pytest.fixture

def sample_data():

    # Подготовка данных

    data = {"user": "testuser", "password": "password123"}

    return data

def test_login(sample_data):

    # Использование данных из фикстуры

    assert "user" in sample_data

    assert sample_data["password"] != ""

Магия происходит в момент, когда Pytest видит, что тестовая функция принимает аргумент с именем фикстуры. Фреймворк автоматически запускает фикстуру и передает ее результат в тест. Это как если бы ваш любимый бариста готовил вам кофе именно так, как вы любите, еще до того как вы зашли в кафе.

Фикстуры могут иметь разные области действия. По умолчанию они пересоздаются для каждой функции, но это можно изменить с помощью параметра scope:

@pytest.fixture(scope="module")

def database_connection():

    # Дорогостоящая операция подключения к БД

    conn = connect_to_database()

    yield conn  # Вместо return используем yield для запуска кода после теста

    conn.close()  # Этот код выполнится, когда все тесты модуля завершатся

Возможные значения для scope: function (по умолчанию), class, module, package или session. Последний вариант означает, что фикстура создается один раз на всю тестовую сессию — удобно для особенно «дорогих» операций.

Иногда вам нужно, чтобы фикстура запускалась всегда, даже если тест её явно не запрашивает. Например, вы хотите очищать базу данных перед каждым тестом. Для этого есть параметр autouse=True:

@pytest.fixture(autouse=True)

def clean_database():

    # Код, который выполняется перед каждым тестом

    database.clean()

    yield

    # Код, который выполняется после каждого теста

    database.close()

Фикстуры могут также принимать параметры. Вот как это работает:

@pytest.fixture

def create_user(request):

    username = request.param

    # Создаем пользователя

    user = User(username=username)

    return user

@pytest.mark.parametrize("create_user", ["admin", "guest"], indirect=True)

def test_user_access(create_user):

    # Тест запустится дважды: один раз с пользователем "admin", другой -- с "guest"

    assert create_user.username in ["admin", "guest"]

Параметризация тестов

Параметризация — это способ запустить один и тот же тест с разными входными данными. Это как устроить дегустацию: одна и та же процедура (попробовать вино), но разные сорта вин.

параметризация позволяет создавать несколько случаев теста из одного определения

Столбчатая диаграмма сравнивает обычный тест (1 запуск) и параметризованный (4 запуска). Показывает, как параметризация позволяет легко создавать несколько случаев теста из одного определения.

Без параметризации пришлось бы писать несколько очень похожих тестов:

def test_add_2_and_3():

    assert add(2, 3) == 5

def test_add_0_and_0():

    assert add(0, 0) == 0

def test_add_negative():

    assert add(-1, -1) == -2

С параметризацией это превращается в один тест:

@pytest.mark.parametrize("a, b, expected", [

    (2, 3, 5),

    (0, 0, 0),

    (-1, -1, -2),

    (0.1, 0.2, 0.3)  # Осторожно с этим из-за погрешностей float!

])

def test_add(a, b, expected):

    assert add(a, b) == expected

Параметризация особенно полезна в следующих сценариях:

  1. Граничные случаи — когда нужно проверить работу функции на краях допустимых значений
  2. Проверка обработки ошибок — разные типы исключений для разных входных данных
  3. Регрессионное тестирование — когда нужно убедиться, что исправленный баг не возвращается

Группировка и управление тестами с помощью меток

Метки в Pytest — это как теги на YouTube: они помогают организовать контент и быстро найти то, что вам нужно. С помощью меток вы можете группировать тесты по различным критериям и запускать только те, которые соответствуют вашим текущим задачам.

Для добавления метки используется декоратор @pytest.mark.*:

@pytest.mark.slow

def test_complex_calculation():

    # Какие-то длительные вычисления

    result = calculate_universal_answer()

    assert result == 42

Теперь вы можете запустить только тесты с меткой «slow»:

$ pytest -m slow

Или, что часто бывает полезнее, исключить их:

$ pytest -m "not slow"

Можно комбинировать метки с использованием логических операторов:

$ pytest -m "slow and important"

$ pytest -m "regression or critical"

Pytest предлагает несколько встроенных меток. Например, skip позволяет пропустить тест:

Можно пропускать тесты условно:

@pytest.mark.skip(reason="Функционал еще не реализован")

def test_futuristic_feature():

    # Тест для функционала, который еще в разработке

    pass
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Требуется Python 3.8+")

def test_new_language_feature():

    # Тест, использующий фичи Python 3.8+

    pass

Есть также метка xfail, которая говорит Pytest, что тест, скорее всего, не пройдет — и это нормально:

@pytest.mark.xfail(reason="Известный баг #42")

def test_known_bug():

    # Тест, который не проходит из-за известного бага

    assert complex_function() == expected_result

Такие тесты будут выполняться, но их провал не будет считаться ошибкой всего набора тестов. Это удобно, когда вы хотите задокументировать проблему, но не хотите, чтобы CI/CD сборка проваливалась из-за неё.

Отчеты и отладка тестов

Отчеты об ошибках в Pytest — это как подробное вскрытие, только для вашего кода. Вместо сухого «тест не прошел» вы получаете детальное описание того, что пошло не так, где именно произошла ошибка, и какие значения переменных привели к этому плачевному результату.

Когда тест не проходит, Pytest выводит примерно такую информацию:

================ FAILURES ================

________________ test_equality ________________

    def test_equality():

>       assert 1 == 2

E       assert 1 == 2

test_sample.py:3: AssertionError

Это базовый отчет, но Pytest умеет гораздо больше. Например, при сравнении сложных структур данных он покажет, чем именно они отличаются:

def test_complex_data():

    actual = {"name": "John", "scores": [1, 2, 3], "active": True}

    expected = {"name": "John", "scores": [1, 2, 4], "active": False}

    assert actual == expected

Результат такого провала будет весьма информативным:

E       assert {'active': True, 'name': 'John', 'scores': [1, 2, 3]} == {'active': False, 'name': 'John', 'scores': [1, 2, 4]}

E         Differing items:

E         {'active': True} != {'active': False}

E         {'scores': [1, 2, 3]} != {'scores': [1, 2, 4]}

Для управления форматом вывода трейсбека (той самой информации об ошибке), можно использовать опцию —tb:

$ pytest --tb=short  # Краткая информация

$ pytest --tb=long   # Подробная информация

$ pytest --tb=native # Стандартный трейсбек Python

Когда у вас много тестов, но вы хотите сосредоточиться только на тех, которые падают, можно использовать опцию -xvs:

$ pytest -xvs

Здесь:

  • -x останавливает выполнение после первой ошибки
  • -v включает подробный вывод
  • -s показывает вывод из функций (print и т.д.)

Если вы хотите ограничить количество ошибок перед остановкой, используйте —maxfail:

$ pytest --maxfail=2  # Остановка после двух ошибок

Для повторного запуска только упавших тестов (а не всего набора), что экономит время при отладке, есть опция —lf (last failed):

$ pytest --lf

А если вы хотите сначала запустить проваленные тесты, а потом все остальные, используйте —ff (failed first):

$ pytest --ff

Для особо сложных случаев можно включить режим pdb (Python Debugger) при падении теста:

$ pytest --pdb

Это запустит интерактивный отладчик в точке, где произошла ошибка, позволяя вам исследовать состояние программы. Для тех, кто привык к print-отладке, это примерно как получить возможность говорить с духом только что умершего кода.

Наконец, для генерации красочных HTML-отчетов можно использовать плагин pytest-html:

$ pip install pytest-html

$ pytest --html=report.html

Получившийся отчет можно отправить коллегам или приложить к документации — это гораздо профессиональнее, чем скриншоты терминала, особенно если вам нужно продемонстрировать боссу, что вы действительно тестировали тот функционал, который он просил.

Плагины и дополнительные возможности

Что такое плагины? Это дополнительные модули, которые расширяют функциональность Pytest. Они могут добавлять новые команды, отчеты, фикстуры и множество других возможностей. На сегодня существует более 1000 плагинов для Pytest, и это число продолжает расти быстрее, чем количество новых криптовалют.

Вот таблица с наиболее популярными плагинами, которые стоит иметь в своем арсенале:

Плагин Назначение Что делает Как установить
pytest-cov Анализ покрытия кода Показывает, какой процент кода покрыт тестами и какие строки остались непроверенными pip install pytest-cov
pytest-xdist Параллельное выполнение Распределяет тесты по нескольким процессорам, ускоряя выполнение в несколько раз pip install pytest-xdist
pytest-mock Мокирование объектов Упрощает создание подставных объектов для изоляции тестируемого кода pip install pytest-mock
pytest-django Тестирование Django Упрощает тестирование Django-приложений pip install pytest-django
pytest-asyncio Асинхронные тесты Добавляет поддержку тестирования асинхронного кода pip install pytest-asyncio
pytest-rerunfailures Повторный запуск Автоматически перезапускает проваленные тесты несколько раз pip install pytest-rerunfailures
pytest-timeout Таймауты Прерывает зависшие тесты после определенного времени pip install pytest-timeout
pytest-benchmark Бенчмаркинг Измеряет производительность кода pip install pytest-benchmark

Использование плагинов обычно очень простое. Установите их через pip, и они автоматически интегрируются с Pytest. Например, чтобы проверить покрытие тестами с помощью pytest-cov:

$ pytest --cov=myapp tests/

Эта команда запустит тесты и покажет, какой процент кода в пакете myapp покрыт тестами. Результат будет выглядеть примерно так:

---------- coverage: platform linux, python 3.8.10-final-0 -----------

Name                      Stmts   Miss  Cover

---------------------------------------------

myapp/__init__.py             1      0   100%

myapp/models.py              95     35    63%

myapp/views.py              102     58    43%

---------------------------------------------

TOTAL                       198     93    53%

53% покрытия может показаться не очень впечатляющим результатом, но, по крайней мере, теперь вы знаете, где сосредоточить свои усилия.

Для параллельного выполнения тестов с помощью pytest-xdist:

$ pytest -n 4  # Запуск на 4 процессах

Для автоматического перезапуска нестабильных тестов:

$ pytest --reruns 3  # Перезапуск проваленных тестов до 3 раз

Некоторые плагины добавляют новые фикстуры. Например, pytest-mock добавляет фикстуру mocker, которая упрощает создание подставных объектов:

def test_get_data(mocker):

    # Создаем мок для API-клиента

    mock_api = mocker.patch('myapp.api.client')

    mock_api.get_data.return_value = {'status': 'ok', 'data': [1, 2, 3]}

   

    # Тестируем функцию, которая использует API-клиент

    result = get_user_data(123)

    assert result == [1, 2, 3]

Еще один важный аспект плагинов — расширение возможностей генерации отчетов. Например, pytest-html создает HTML-отчеты с графиками и таблицами, а pytest-reportlog генерирует машиночитаемые логи для дальнейшего анализа.

Если вам нужна функциональность, которой нет в существующих плагинах, вы всегда можете написать свой плагин. Pytest имеет хорошо документированное API хуков, которое позволяет встраиваться практически в любую часть процесса тестирования. Это как создать собственный аксессуар для iPhone, когда на рынке нет ничего подходящего.

Итоги

После всего сказанного должно быть ясно, почему Pytest стал золотым стандартом для тестирования Python-кода. Это как швейцарский нож в мире тестирования — компактный, универсальный и невероятно эффективный, когда вы знаете, как им пользоваться.

Давайте подытожим, почему Pytest — это ваш лучший выбор:

  • Минимальный синтаксис — тесты пишутся как обычные функции, без громоздких классов и методов.
  • Информативные отчеты об ошибках — вы точно знаете, что сломалось и почему.
  • Мощные фикстуры — для организации тестовых данных и повторного использования кода.
  • Гибкая параметризация — запуск одного теста с разными входными данными.
  • Обширная экосистема плагинов — расширяйте функциональность под свои нужды.
  • Сообщество и поддержка — множество ресурсов и активное сообщество разработчиков.

Если хотите попробовать себя в востребованной IT-профессии, то изучите курсы Python разработчика. На них не только расскажут теорию, но и дадут практическую часть. Начните изучать новую профессию уже сегодня!

Читайте также
Guzzle
#Блог

Всё о Guzzle PHP: настройка, запросы и асинхронные операции

Если вы хотите упростить работу с HTTP-запросами, Guzzle PHP станет вашим лучшим другом. Узнайте, как настроить и использовать этот инструмент для синхронных и асинхронных запросов, а также для эффективной обработки ошибок.

Категории курсов
';