Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Задачи

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

AI info

Карта сайта

Документы

Медиа

Назад

Как писать негативные тесты с ожиданием исключений?

Вопрос проверяет умение тестировать ошибочные сценарии, корректно проверять исключения и формировать надёжные проверки поведения кода.

Короткий ответ

В pytest для ожидания исключений используют pytest.raises. Внутрь контекста помещают код, который должен упасть. Дополнительно можно проверить тип исключения и текст сообщения. Это делает негативные тесты точными и защищает от “случайных” падений по другой причине.

Длинный ответ

Определение

Негативный тест — тест, который проверяет, что система корректно обрабатывает неверный ввод или ошибочное состояние (обычно через исключение или возвращаемую ошибку).


Зачем вообще проверять исключения

Перед практикой важно понимать: исключение — часть контракта функции. Если функция должна “падать” на неверном вводе, тесты должны зафиксировать:

  1. какой именно тип ошибки возникает

  2. в каком месте и при каких условиях

  3. что ошибка не скрывается и не превращается в “тихий” неправильный результат


Базовый способ: pytest.raises

1) Проверка только типа исключения

Это минимальный корректный вариант.

import pytest

def parse_age(value: str) -> int:
    age = int(value)
    if age < 0:
        raise ValueError("age must be non-negative")
    return age

def test_parse_age_raises_on_text():
    with pytest.raises(ValueError):
        parse_age("abc")

2) Проверка типа + сообщения

Это полезно, когда важен смысл ошибки (и вы хотите защититься от “падения не там”).

def test_parse_age_raises_on_negative():
    with pytest.raises(ValueError, match="non-negative"):
        parse_age("-1")

Важно: match — это регулярное выражение, поэтому лучше проверять устойчивую часть текста.


3) Сохранение исключения и доп.проверки

Иногда нужно проверить поля исключения или точное сообщение.

def test_parse_age_error_details():
    with pytest.raises(ValueError) as exc_info:
        parse_age("-1")

    assert "non-negative" in str(exc_info.value)

Как писать негативные тесты правильно

1) Точно ограничивать “зону падения”

Одна из самых частых ошибок — слишком широкий блок raises, куда попадает лишний код.

Плохо:

with pytest.raises(ValueError):
    obj = make_obj()      # может упасть само по себе
    result = obj.run()    # а вы думаете, что падает тут

Лучше:

obj = make_obj()
with pytest.raises(ValueError):
    obj.run()

2) Проверять “не упало” там, где нужно

Если функция должна падать только на неверном вводе — добавьте позитивный тест рядом.

def test_parse_age_ok():
    assert parse_age("10") == 10

3) Проверять контракт, а не реализацию

Если вы проверяете внутренние детали (точный текст исключения, класс в глубине), тесты станут хрупкими. Обычно достаточно:

  1. тип исключения

  2. стабильный фрагмент сообщения

  3. код/атрибут ошибки, если он есть (лучше, чем текст)


4) Параметризовать наборы плохих входов

Это резко повышает покрытие и снижает дублирование.

import pytest

@pytest.mark.parametrize("value", ["", "abc", "10.5"])
def test_parse_age_bad_inputs(value):
    with pytest.raises(ValueError):
        parse_age(value)

Исключения и асинхронный код

1) Исключение в async-функции

Контекст тот же, но нужно await.

import pytest

async def fetch_user(user_id: int) -> dict:
    if user_id <= 0:
        raise ValueError("invalid user_id")
    return {"id": user_id}

@pytest.mark.asyncio
async def test_fetch_user_raises():
    with pytest.raises(ValueError, match="invalid"):
        await fetch_user(0)

2) Проверка ошибок в генераторах

Исключение может возникать при итерации.

def gen():
    yield 1
    raise RuntimeError("boom")

def test_generator_raises_on_next():
    g = gen()
    assert next(g) == 1
    with pytest.raises(RuntimeError, match="boom"):
        next(g)

Что делать, если код “проглатывает” ошибки

Иногда система ловит исключение и возвращает результат “с ошибкой”. Тогда негативный тест должен проверять именно этот контракт:

  1. возвращаемую структуру ошибки

  2. код ошибки

  3. флаг успеха

  4. логирование (если это часть требований)

Пример идеи (без привязки к фреймворку):

def do_action():
    try:
        ...
    except ValueError:
        return {"ok": False, "error": "bad_input"}

Тест:

def test_do_action_returns_error():
    result = do_action()
    assert result["ok"] is False
    assert result["error"] == "bad_input"

Типовые ошибки в негативных тестах

  1. Тест “проходит” даже если код упал по другой причине (слишком широкий raises)

  2. Проверяется текст ошибки целиком (хрупко)

  3. Нет позитивного теста рядом, и контракт не очевиден

  4. Проверяется неправильный тип исключения (слишком общий Exception)

  5. Тесты не различают ошибки валидации и ошибки инфраструктуры


Краткий вывод

Негативные тесты в pytest корректнее всего писать через pytest.raises, ограничивая блок только кодом, который должен упасть, и проверяя тип исключения (и при необходимости устойчивую часть сообщения). Такой подход делает тесты точными, устойчивыми и полезными при регрессиях.

  • Аватар

    Python Guru

    Sergey Filichkin

    Guru – это эксперты YeaHub, которые помогают развивать комьюнити.

Уровень

  • Рейтинг:

    5

  • Сложность:

    6

Навыки

  • PyTest

Ключевые слова

#pytest

#negative

#test

Подпишись на Python Developer в телеграм

  • Аватар

    Python Guru

    Sergey Filichkin

    Guru – это эксперты YeaHub, которые помогают развивать комьюнити.