Функции, которые вызывают исключения
Функция Python, получившая неверные входные данные, должна сообщить об этом, вызвав исключение: при успехе она возвращает результат, при неудаче вызывает ошибку — например, ValueError, — которая объясняет, что пошло не так.
Тест для такой функции должен проверять оба пути: при верных входных данных возвращается правильное значение и ничего не вызывается, а при неверных входных данных исключение действительно вызывается — функция, которая молча принимает неверные данные, тоже содержит ошибку.
pytest.raises — инструмент для плохого пути: блок with, который проваливает тест, если из него не вышло ожидаемое исключение.
Хороший путь
Для верных входных данных ничего дополнительного писать не нужно. Вызовите функцию, затем сравните значение. Если вместо этого вызов вызовет исключение, оно само завершит тест, и отчёт это покажет — заранее проверять это не нужно.
Верные входные данные, без исключения
safe_divide(10, 2) должна вернуть 5 и не должна вызывать исключение. Сначала идёт вызов; неожиданное исключение завершило бы тест прямо здесь. Только после этого сравнивается значение.
Вывод: полная программа на Python
def safe_divide(a, b):
if b == 0:
raise ValueError("нельзя делить на ноль")
return a / b
def test_safe_divide_good():
got = safe_divide(10, 2)
assert got == 5
Плохой путь
Вторая половина контракта: неверные входные данные должны вызывать исключение. Вызов помещается в блок with pytest.raises(ValueError) — если функция вместо исключения возвращает число, тест проваливается.
Важно указать класс исключения: блок принимает только ValueError, поэтому другая, неожиданная ошибка всё равно провалит тест.
Неверные входные данные должны вызывать исключение
Деление на ноль должно завершаться ошибкой. pytest.raises(ValueError) оборачивает вызов safe_divide(10, 0). Функция, как и обещано, вызывает исключение, поэтому тест проходит.
Вывод: полная программа на Python
import pytest
def safe_divide(a, b):
if b == 0:
raise ValueError("нельзя делить на ноль")
return a / b
def test_safe_divide_by_zero():
with pytest.raises(ValueError):
safe_divide(10, 0)
Таблица с want_error
Оба пути помещаются в один параметризованный тест: дайте каждому случаю флаг want_error, который говорит, ожидается ли исключение. Значение want для такого случая равно None, потому что у случая, вызывающего исключение, нет значения для сравнения.
Тест сначала смотрит на want_error. Ожидаемая ошибка проверяется с помощью pytest.raises, и на этом всё; в остальных случаях значение сравнивается как обычно.
Хорошие и плохие случаи в одной таблице
Четыре случая: три обычных деления и одно деление на ноль с want_error=True. Один параметризованный тест покрывает весь контракт функции, и все четыре случая проходят.
Вывод: полная программа на Python
import pytest
def safe_divide(a, b):
if b == 0:
raise ValueError("нельзя делить на ноль")
return a / b
@pytest.mark.parametrize("a, b, want, want_error", [
(10, 2, 5, False),
(-9, 3, -3, False),
(0, 5, 0, False),
(10, 0, None, True),
], ids=["simple_division", "negative_result", "divide_zero", "divide_by_zero"])
def test_safe_divide(a, b, want, want_error):
if want_error:
with pytest.raises(ValueError):
safe_divide(a, b)
else:
assert safe_divide(a, b) == want