Pytest в Python-разработке: простое создание и запуск тестов кода

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Python-разработчики, желающие улучшить качество своих проектов через тестирование.
  • Начинающие тестировщики ПО, заинтересованные в освоении инструментов для тестирования на Python.
  • Команды разработчиков и QA-специалистов, стремящиеся интегрировать автоматизированное тестирование в CI/CD процессы.

    Когда у вас код разрастается до нескольких сотен или тысяч строк, ошибки становятся неизбежными. Один пропущенный тест — и вместо гениального продукта получаете репутационную катастрофу. Pytest спасает положение, позволяя легко создавать, запускать и поддерживать тесты любой сложности. В отличие от встроенного unittest, он проще в освоении и мощнее в применении — идеально для Python-разработчиков всех уровней. Давайте разберемся, как внедрить pytest в рабочий процесс и превратить тестирование из головной боли в удовольствие. 🚀

Хотите стать востребованным IT-специалистом и научиться писать качественные тесты на Python? Курс тестировщика ПО от Skypro — идеальный старт карьеры в QA. Вы не только освоите pytest и другие фреймворки тестирования, но и получите практические навыки работы с реальными проектами под руководством опытных менторов. 94% выпускников трудоустраиваются в течение 3 месяцев после завершения программы. Инвестируйте в свое будущее прямо сейчас!

Pytest для Python: основы установки и настройки среды

Установка pytest — первый шаг на пути к грамотному тестированию кода. В отличие от встроенного модуля unittest, pytest требует отдельной установки, но предоставляет значительно более мощный и гибкий инструментарий.

Установка выполняется стандартным способом через pip:

pip install pytest

Для проверки корректности установки выполните команду:

pytest --version

Если вы видите версию pytest, установка прошла успешно. 👍

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

  • project_root/ — корневая директория проекта
  • project_root/src/ или project_root/mypackage/ — исходный код приложения
  • project_root/tests/ — директория с тестами
  • project_root/tests/conftest.py — файл с общими фикстурами

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

Настройка среды тестирования часто требует специфических параметров и плагинов. Для этого можно создать файл pytest.ini в корне проекта:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers = 
slow: marks tests as slow
integration: marks tests as integration tests

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

Плагин Назначение Команда установки
pytest-cov Измерение покрытия кода тестами pip install pytest-cov
pytest-xdist Параллельный запуск тестов pip install pytest-xdist
pytest-mock Создание mock-объектов pip install pytest-mock
pytest-timeout Отслеживание тайм-аутов тестов pip install pytest-timeout

Алексей Петров, Lead Python Developer

Помню, как моей команде поручили разработать микросервис для обработки платежей. Мы писали код в спешке, без тестов, думая: "Напишем потом". В пятницу вечером выкатили в продакшн — и понеслось. Посыпались баги, серверы падали, клиенты теряли деньги.

За выходные пришлось переписать систему с нуля, но уже с полным покрытием pytest-тестами. Создали фикстуры для эмуляции платежного шлюза, параметризованные тесты для проверки разных сценариев, настроили CI/CD для автоматического прогона перед каждым деплоем.

Результат? За следующий месяц — ни одного инцидента. Руководство выделило бонусы команде, а тестирование с pytest стало обязательным стандартом для всех проектов компании. Этот опыт убедил меня: час на написание тестов экономит дни на исправление ошибок.

Пошаговый план для смены профессии

Создание первых тестов с pytest: структура и синтаксис

Создание тестов в pytest отличается изящной простотой по сравнению с другими фреймворками. Базовая структура теста — это обычная функция Python с префиксом test_:

Python
Скопировать код
# test_example.py
def test_simple_addition():
assert 1 + 1 == 2

В отличие от unittest, здесь нет необходимости создавать классы или наследовать их от базовых классов тестирования. Pytest автоматически обнаружит и запустит все функции, начинающиеся с test_.

Ключевой элемент тестирования в pytest — оператор assert. В отличие от стандартных ассертов Python, pytest предоставляет расширенную диагностику при сбоях, что упрощает отладку:

Python
Скопировать код
def test_complex_data_comparison():
data1 = {'key1': [1, 2, 3], 'key2': 'value'}
data2 = {'key1': [1, 2, 3], 'key2': 'different'}
assert data1 == data2 # Pytest покажет точное различие

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

Python
Скопировать код
class TestCalculator:
def test_addition(self):
assert 1 + 1 == 2

def test_subtraction(self):
assert 3 – 1 == 2

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

Фикстуры (fixtures) — мощный механизм pytest для подготовки тестового окружения. Они позволяют создавать и настраивать объекты, которые можно повторно использовать в различных тестах:

Python
Скопировать код
# conftest.py
import pytest

@pytest.fixture
def sample_data():
return {'name': 'Test User', 'age': 30}

# test_user.py
def test_user_name(sample_data):
assert sample_data['name'] == 'Test User'

def test_user_age(sample_data):
assert sample_data['age'] == 30

Фикстуры могут иметь различные области действия:

Область (scope) Описание Типичное применение
function (по умолчанию) Выполняется для каждого теста Подготовка данных для отдельного теста
class Выполняется один раз для класса Общие данные для связанных тестов
module Выполняется один раз для модуля Общий ресурс для тестов в файле
session Выполняется один раз за сессию Подключение к БД или другие тяжелые ресурсы

Пример фикстуры с областью действия и функцией очистки:

Python
Скопировать код
@pytest.fixture(scope="module")
def database_connection():
# Настройка: подключение к тестовой БД
conn = create_test_db_connection()
yield conn # Предоставляем соединение тестам

# Очистка после использования
conn.close()

Запуск и анализ результатов тестирования в pytest

Запуск тестов с pytest отличается гибкостью и информативностью. Базовый запуск всех тестов в текущей директории выполняется простой командой:

pytest

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

  • Конкретный тестовый файл: pytest tests/test_calculator.py
  • Конкретная тестовая функция: pytest tests/test_calculator.py::test_addition
  • Тесты по маркерам: pytest -m "slow" (для тестов, помеченных декоратором @pytest.mark.slow)
  • Тесты по ключевым словам: pytest -k "addition" (запустит тесты, содержащие слово "addition" в имени)

Параметры запуска могут значительно улучшить информативность отчетов:

  • -v или --verbose: подробный режим с детальной информацией о каждом тесте
  • -s: отображение вывода print() из тестов (по умолчанию stdout перехватывается)
  • --tb=short: сокращенный вывод трейсбека ошибок
  • --cov=mypackage: расчет покрытия кода тестами (требует pytest-cov)

Интерпретация результатов тестирования в pytest интуитивно понятна благодаря цветовой кодировке:

  • PASS (.): тест пройден успешно
  • FAIL (F): тест завершился с ошибкой (сработал assert)
  • ERROR (E): произошла непредвиденная ошибка во время выполнения теста
  • SKIP (s): тест пропущен (например, с декоратором @pytest.mark.skip)
  • XFAIL (x): ожидаемый провал теста (с @pytest.mark.xfail)
  • 🟠 XPASS (X): тест, помеченный как ожидаемый провал, но успешно прошедший

Вот пример вывода запуска тестов:

============================= test session starts ==============================
platform linux -- Python 3.10.0, pytest-7.3.1
rootdir: /home/user/projects/my_project
collected 5 items

tests/test_calculator.py ..F.s [100%]

=================================== FAILURES ===================================
________________________________ test_division _________________________________

def test_division():
> assert 1 / 0 == float('inf')
E ZeroDivisionError: division by zero

tests/test_calculator.py:15: ZeroDivisionError
=========================== short test summary info ===========================
FAILED tests/test_calculator.py::test_division
========================= 1 failed, 3 passed, 1 skipped in 0.12s =============

Для анализа покрытия кода тестами используйте плагин pytest-cov:

pytest --cov=mypackage tests/

Это даст базовую статистику по покрытию. Для более детального отчета:

pytest --cov=mypackage --cov-report=html tests/

Эта команда создаст HTML-отчет в директории htmlcov/, который показывает покрытие с точностью до строки кода. 📊

Продвинутые техники тестирования с использованием pytest

Марина Сидорова, QA Lead

В прошлом году наша команда столкнулась с классической проблемой: тесты работали на локальных машинах, но регулярно падали в CI-среде. Причина оказалась в параллельном запуске, который выявлял гонки данных.

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

Особенно помогла техника mock-объектов для эмуляции внешних API. Вместо реальных запросов к платежному шлюзу мы использовали monkeypatch для подстановки тестовых ответов с разными сценариями: успешный платеж, отказ, таймаут.

После внедрения этих практик стабильность нашей CI-системы выросла с 68% до 99,5%. Время прогона всех тестов сократилось с 40 до 12 минут благодаря грамотной параллелизации с pytest-xdist. Главный урок: инвестиции в продвинутые техники тестирования окупаются многократно.

Параметризация тестов — одна из самых мощных функций pytest. Она позволяет запускать один и тот же тест с различными входными данными, что значительно сокращает дублирование кода:

Python
Скопировать код
@pytest.mark.parametrize("input_value,expected_output", [
(1, 1), # Факториал 1 равен 1
(2, 2), # Факториал 2 равен 2
(3, 6), # Факториал 3 равен 6
(4, 24), # Факториал 4 равен 24
])
def test_factorial(input_value, expected_output):
from math import factorial
assert factorial(input_value) == expected_output

Использование моков (mocks) и патчей (patches) часто необходимо для изоляции тестируемого кода от внешних зависимостей:

Python
Скопировать код
def test_api_call(monkeypatch):
# Создаем имитацию функции запроса к API
def mock_get_json(*args, **kwargs):
return {"status": "success", "data": [1, 2, 3]}

# Заменяем реальную функцию нашей имитацией
monkeypatch.setattr("requests.get", mock_get_json)

# Тестируем функцию, которая внутри вызывает requests.get
result = fetch_data_from_api()
assert result == [1, 2, 3]

Фикстуры с очисткой (teardown) позволяют корректно освобождать ресурсы после тестов:

Python
Скопировать код
@pytest.fixture
def temp_file():
# Создаем временный файл
file_path = "temp_test_file.txt"
with open(file_path, "w") as f:
f.write("Test content")

# Передаем путь к файлу тестам
yield file_path

# После завершения теста удаляем файл
import os
os.remove(file_path)

Обработка исключений — важная часть тестирования. Pytest предоставляет удобный способ проверки исключений:

Python
Скопировать код
def divide(a, b):
if b == 0:
raise ValueError("Division by zero")
return a / b

def test_divide_by_zero():
with pytest.raises(ValueError) as excinfo:
divide(10, 0)
assert "Division by zero" in str(excinfo.value)

Маркировка тестов помогает категоризировать их и управлять запуском:

Python
Скопировать код
@pytest.mark.slow
def test_complex_calculation():
# Длительный тест
pass

@pytest.mark.integration
def test_database_integration():
# Тест с реальной БД
pass

Настройка пропуска тестов при определенных условиях:

Python
Скопировать код
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires Python 3.8 or higher")
def test_new_feature():
# Тест для функции, доступной только в Python 3.8+
pass

Для работы с асинхронным кодом используется плагин pytest-asyncio:

Python
Скопировать код
import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_function():
result = await my_async_function()
assert result == expected_value

Интеграция pytest в процесс разработки: автоматизация и CI/CD

Интеграция тестов в процесс непрерывной разработки (CI/CD) значительно повышает качество кода. Настроенные правильно автоматические тесты помогают выявлять проблемы на ранних стадиях, до того как они попадут в продакшн. 🔄

Для интеграции pytest в популярные CI/CD системы используйте следующие конфигурации:

CI/CD платформа Конфигурационный файл Ключевые параметры
GitHub Actions .github/workflows/test.yml triggers, matrix, environment, cache
GitLab CI .gitlab-ci.yml stages, image, script, artifacts
Jenkins Jenkinsfile pipeline, stages, steps, post
CircleCI .circleci/config.yml jobs, workflows, executors

Пример конфигурации для GitHub Actions:

yaml
Скопировать код
name: Python Tests

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3\.8, 3.9, 3.10]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
pytest --cov=mypackage tests/ --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1

Автоматизация запуска тестов при коммитах с помощью pre-commit хуков экономит время и предотвращает отправку нерабочего кода в репозиторий:

yaml
Скопировать код
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pytest-check
name: pytest-check
entry: pytest
language: system
pass_filenames: false
always_run: true

Установка и активация pre-commit:

pip install pre-commit
pre-commit install

Оптимизация времени выполнения тестов в CI/CD критична для быстрого получения обратной связи. Стратегии оптимизации включают:

  • Параллельное выполнение с помощью pytest-xdist: pytest -n auto
  • Приоритизация тестов — запуск быстрых тестов первыми: pytest --run-fast-first
  • Инкрементальное тестирование — запуск только тестов, затронутых изменениями
  • Кеширование результатов между запусками: pytest --cache-clear для сброса кеша

Интеграция с системами отслеживания ошибок (issue trackers) позволяет автоматически создавать задачи при падении тестов:

pytest --jira-xfail=XYZ-123

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

Python
Скопировать код
@pytest.mark.xfail(reason="Bug #123 – this will be fixed in next release")
def test_known_failure():
# Если этот тест внезапно начнет проходить, нужно обновить документацию
assert problematic_function() == expected_result

Определение "стоп-факторов" в пайплайне CI/CD помогает предотвратить поставку проблемного кода:

  • Падение критических тестов
  • Снижение покрытия кода ниже установленного порога
  • Обнаружение уязвимостей безопасности
  • Превышение порога сложности кода

Непрерывная интеграция тестов в процессе разработки обеспечивает стабильность и высокое качество кода, сокращая затраты на поиск и исправление ошибок в будущем.

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

Загрузка...