Проверьте поведение проверки сообщения исключения: пройдёт ли этот тест, хотя шаблон не совпадает со всей строкой?
import pytest
def parse_port(value):
if not value.isdigit():
raise ValueError(f"invalid port: {value}")
def test_parse_port():
with pytest.raises(ValueError, match="port: 80"):
parse_port("8080x")
Тест пройдёт. Параметр match в pytest.raises интерпретируется как регулярное выражение и проверяется поиском совпадения внутри текста исключения, а не сравнением всей строки.
Сообщение исключения — "invalid port: 8080x", а шаблон "port: 80" находит в нём подходящий фрагмент. Для проверки всей строки нужны якоря регулярного выражения ^ и $ либо явно экранированный ожидаемый текст.
Проверка исключений появилась как более точная альтернатива ручной конструкции try/except, где тест должен был самостоятельно определить, было ли исключение выброшено и правильно ли сформировано его сообщение.
В pytest контекстный менеджер pytest.raises отделяет проверку типа исключения от проверки его текста. Поддержка регулярных выражений позволяет проверять устойчивую часть сообщения, не привязывая тест к динамическим данным.
Если разработчик ожидает полное равенство сообщения, но передаёт в match обычную строку без якорей, тест может пропустить неправильный текст. Это создаёт ложноположительный результат: исключение имеет нужный тип, но сообщение содержит неожиданные дополнительные данные или даже другой контекст.
Обратная ситуация тоже опасна. Если динамическое значение содержит символы вроде ., +, [ или ?, строка в match будет обработана как регулярное выражение, а не как буквальный текст.
pytest.raises проверяет три основных условия:
match текст исключения совпадает с регулярным выражением.Проверка match выполняется поиском по строковому представлению исключения, аналогично re.search. Поэтому шаблон может соответствовать только части сообщения:
Якоря ^ и $ требуют совпадения с началом и концом строки. re.escape превращает пользовательский или динамический текст в буквальный шаблон и предотвращает интерпретацию специальных символов регулярного выражения.
Важно, что match проверяет текст исключения, а не его атрибуты. Для исключений с дополнительными данными, например кодом ошибки, такие поля следует проверять отдельно через объект, возвращённый из контекстного менеджера:
Слишком точная проверка всего сообщения повышает чувствительность теста к косметическим изменениям текста. Проверка только короткого фрагмента, напротив, может скрыть ошибку. Выбор зависит от контракта: если сообщение является публичным API, проверяйте его точнее; если важен только диагностический фрагмент, используйте частичное совпадение.
В API-сервисе ошибка валидации содержала имя поля и значение, например "invalid port: 8080x". Команда сначала использовала match="invalid port", поэтому тест продолжал проходить даже после изменения формата сообщения на "invalid port; request rejected".
Рассматривались два варианта. Полное сравнение строки было строгим, но делало тест хрупким при добавлении диагностических деталей. Частичное регулярное выражение было стабильнее, однако не гарантировало корректность всего сообщения.
Выбрали проверку стабильного контракта через match=r"^invalid port: 8080x$" для отдельного теста формата и отдельные тесты атрибутов исключения для машинно обрабатываемых данных. Это разделило проверку пользовательского текста и проверку структурированных полей.
match="text" полному сравнению сообщения?Нет. Это регулярное выражение, применяемое поиском внутри текста исключения. Полное совпадение нужно выразить якорями, например match=r"^text$", учитывая, что перенос строки и особенности регулярного выражения тоже влияют на результат.
Нельзя бездумно передавать переменную в match, если она может содержать специальные символы regex. Следует использовать re.escape(expected); если требуется полное совпадение, добавить якоря: match=rf"^{re.escape(expected)}$".
pytest.raises исключение не возникнет?Тест завершится ошибкой, потому что контекстный менеджер ожидает исключение. Если будет выброшен другой тип, тест также завершится ошибкой; match в таком случае не заменяет проверку типа и не делает любой тип исключения допустимым.