pytest.raises: как проверить отказ и сохранность состояния
Тест ожидает ValueError и проходит. Через неделю выясняется: функция сначала изменила заказ, затем обнаружила неправильное количество. Исключение получилось правильным, состояние — испорченным. Чтобы проверить отказ целиком, нужно описать и ошибку, и то, чего после неё не произошло.
Сначала определить границу операции
Пусть на складе осталось пять коробок. Запрос на семь коробок должен закончиться отказом; остаток обязан остаться равным пяти. Здесь два наблюдаемых результата: вызов сообщает о нехватке товара, а склад сохраняет прежнее количество. Если проверять только первый, перестановка двух строк в реализации останется незамеченной.
Отдельно решите, что означает нулевое и отрицательное количество. В примере они запрещены независимо от остатка. Такая договорённость помогает выбрать случаи по смыслу: отрицательный запрос нарушает правило количества, положительный сверх остатка нарушает правило наличия. Одинаковый тип ошибки ещё не означает одинаковую причину.
Поставить вызов внутрь узкого блока
Внутри pytest.raises оставляем только действие, которое обязано завершиться исключением. Создание склада выполняется выше, проверка остатка — ниже. Код после вызова внутри блока не выполнится: управление сразу перейдёт к обработчику контекстного менеджера. Если поместить туда assert остатка, получится проверка, до которой выполнение вообще не доходит.
Параметр match ищет регулярное выражение в сообщении. Для устойчивого сообщения можно проверять короткий смысловой фрагмент. Если текст содержит скобки, точку или другой специальный знак, используйте re.escape либо явно запишите шаблон. В нашем примере привязка к началу и концу фиксирует ровно ожидаемое сообщение, без случайного совпадения внутри другой ошибки.
Самодостаточный тест склада
Сохраните пример в test_stock.py и выполните python -m pytest -q test_stock.py. Параметризация запускает отдельный тест для каждой причины отказа. Начальный остаток копируем перед операцией: это позволяет сравнивать результат с состоянием, полученным из объекта, а не размножать число пять во всех проверках.
import pytest
class Stock:
def __init__(self, available):
self.available = available
def take(self, amount):
if amount <= 0:
raise ValueError("quantity must be positive")
if amount > self.available:
raise ValueError("not enough stock")
self.available -= amount
return amount
@pytest.mark.parametrize("amount,message", [
(0, "quantity must be positive"),
(-1, "quantity must be positive"),
(7, "not enough stock"),
])
def test_rejected_take_preserves_stock(amount, message):
stock = Stock(5)
before = stock.available
with pytest.raises(ValueError, match="^" + message + "$"):
stock.take(amount)
assert stock.available == before
def test_successful_take_changes_stock():
stock = Stock(5)
assert stock.take(2) == 2
assert stock.available == 3Что изменится при настоящем дефекте
Перенесите вычитание перед проверкой наличия: случай с семью коробками должен упасть на сравнении остатка. Уберите проверку количества: случаи с нулём и минус единицей должны сообщить, что ожидаемого исключения нет. Так у каждого теста появляется конкретная причина существования. В рабочем проекте такую временную поломку делают локально и затем возвращают корректную реализацию.
Если операция записывает файл или вызывает другую систему, одной проверки поля мало. Проверяйте соответствующую границу: содержимое временного файла или отсутствие обращения к явно переданной зависимости. Не перехватывайте Exception на весь тест: ошибка подготовки тогда может случайно удовлетворить ожидание и скрыть то, что нужный метод не был вызван.
Закрепить проверку исключений
В открытом уроке «Первые тесты на pytest» есть обычные сценарии, граничные случаи и проверка ожидаемого исключения. Пройдите эту практику и добавьте в свою проверку сохранность состояния после отказа. Откройте программу курса, чтобы продолжить с параметризацией, фикстурами и регрессионными тестами.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
