Фреймворк pytest
автоматически ловит предупреждения во время выполнения теста и отображает их в конце сеанса.
pytest
;@pytest.mark.filterwarnings()
;DeprecationWarning
и PendingDeprecationWarning
;pytest.warns()
;recwarn
);pytest
.pytest
.Для управления предупреждениями при тестировании, какие нужно игнорировать, какие отображать в конце сеанса, а какие преобразовывать в ошибки, фреймворк pytest
использует опцию CLI -W
. Эта опция работает аналогично флагу -W
интерпретатора Python. Более подробные варианты использования смотрите в материале "Фильтр предупреждений warnings
".
Рассмотрим следующий тест:
# test_warnings.py import warnings def api_v1(): # определяем пользовательское предупреждение warnings.warn(UserWarning("api v1, необходимы функции из v2")) return 1 def test_one(): assert api_v1() == 1
Теперь, что бы pytest
рассматривал категорию предупреждений UserWarning
как ошибку необходимо запустить тест со следующими опциями:
$ pytest -q test_warnings.py -W error::UserWarning
Тот же самое можно установить в файле pytest.ini
или pyproject.toml
с помощью параметра настройки filterwarnings
. Например, приведенная ниже конфигурация будет игнорировать все пользовательские предупреждения UserWarning
и конкретные предупреждения DeprecationWarning
, соответствующие регулярному выражению, при этом все остальные предупреждения будут преобразованы в ошибки.
# файл pytest.ini [pytest] filterwarnings = error ignore::UserWarning ignore:function ham\(\) is deprecated:DeprecationWarning
# файл pyproject.toml [tool.pytest.ini_options] filterwarnings = [ "error", "ignore::UserWarning", # обратите внимание на использование одинарной кавычки # для обозначения "сырых" строк в TOML. 'ignore:function ham\(\) is deprecated:DeprecationWarning', ]
Если предупреждение соответствует нескольким параметрам в списке, выполняется действие для последнего соответствующего параметра.
Примечание. Хотя это и не рекомендуется, можно использовать опцию командной строки
--disable-warnings
, чтобы полностью скрыть сводку предупреждений из выходных данных тестового запуска.
@pytest.mark.filterwarnings()
.Чтобы добавить фильтры предупреждений к определенным элементам теста, можно использовать декоратор @pytest.mark.filterwarnings()
. Этот декоратор позволит более точно контролировать, какие предупреждения должны фиксироваться на уровне теста, класса или даже модуля:
import warnings def api_v1(): warnings.warn(UserWarning("api v1, необходимы функции из v2")) return 1 @pytest.mark.filterwarnings("ignore:api v1") def test_one(): assert api_v1() == 1
Фильтры, примененные с помощью декоратора/метки, имеют приоритет над фильтрами, переданными в командной строке или настроенными с помощью опции конфигурации filterwarnings
.
Можно применить фильтр ко всем тестам класса, используя @pytest.mark.filterwarnings
в качестве декоратора класса, или ко всем тестам в модуле, установив переменную/атрибут модуля pytestmark
:
# переведет все предупреждения в ошибки для этого модуля pytestmark = pytest.mark.filterwarnings("error")
Плагин захвата предупреждений включен в pytest
по умолчанию, но его можно полностью отключить в файле pytest.ini
с помощью:
# pytest.ini [pytest] addopts = -p no:warnings
Или передать опцию -p no:warnings
в командной строке. Это может быть полезно, если наборы тестов обрабатывают предупреждения с помощью внешней системы.
DeprecationWarning
и PendingDeprecationWarning
.По умолчанию pytest
будет отображать предупреждения DeprecationWarning
и PendingDeprecationWarning
из пользовательского кода и сторонних библиотек. Это помогает пользователям поддерживать свой код в актуальном состоянии и избегать сбоев при эффективном удалении устаревших предупреждений.
Иногда полезно скрыть некоторые конкретные предупреждения об устаревании в коде, который невозможно контролировать (например, в сторонних библиотеках). Чтобы игнорировать эти предупреждения можно использовать pytest.ini
или @pytest.mark.filterwarnings()
.
# pytest.ini [pytest] filterwarnings = ignore:.*U.*mode is deprecated:DeprecationWarning
При этом будут игнорироваться все предупреждения типа DeprecationWarning
, в которых начало сообщения соответствует регулярному выражению '.*U.*mode is deprecated'
.
Примечание:
Если предупреждения настроены на уровне интерпретатора с использованием переменной среды
PYTHONWARNINGS
или параметра командной строки-W
, тоpytest
не будет настраивать фильтры по умолчанию.Кроме того,
pytest
не сбрасывает все фильтры предупреждений, т.к. это может нарушить тесты, которые сами настраивают фильтры предупреждений, вызвавwarnings.simplefilter()
.
pytest.deprecated_call()
.Для проверки того, что определенная функция вызывает предупреждение DeprecationWarning
или PendingDeprecationWarning
можно использовать функцию pytest.deprecated_call()
:
import pytest def test_myfunction_deprecated(): with pytest.deprecated_call(): myfunction(17)
Этот тест завершится ошибкой, если myfunction()
не выдаст предупреждение об устаревании при вызове с аргументом 17.
pytest.warns()
.Используя функцию pytest.warns(expected_warning, match=None)
можно проверить, вызывает ли код конкретное предупреждение. Функция pytest.warns()
работает так же, как и pytest.raises()
:
expected_warning
: объект исключения Warning
или кортеж объектов Warning
;Аргумент match
: если указано, то это строка, содержащая регулярное выражение или объект регулярного выражения, который проверяется на соответствие строковому представлению исключения с помощью re.search()
. Может содержать специальные символы, шаблон может быть сначала экранирован с помощью re.escape()
.
Аргумент match
используется только тогда, когда pytest.warns()
используется в качестве диспетчера контекста. При использовании pytest.warns()
в качестве функции можно использовать: pytest.warns(Warn, func, match="passed on").match("my pattern")
import warnings import pytest def test_warning(): with pytest.warns(UserWarning): warnings.warn("my warning", UserWarning)
Если соответствующее предупреждение не возникнет, то тест завершится неудачей. Функция pytest.warns()
имеет ключевой аргумент match
, который принимает регулярное выражение для проверки соответствия тексту предупреждения:
>>> with warns(UserWarning, match=r'must be 0 or None'): ... warnings.warn("value must be 0 or None", UserWarning) >>> with warns(UserWarning, match=r'must be \d+$'): ... warnings.warn("value must be 42", UserWarning) >>> with warns(UserWarning, match=r'must be \d+$'): ... warnings.warn("this is not here", UserWarning) # Traceback (most recent call last): # ... # Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
Также можно вызвать pytest.warns()
для функции или строки кода:
pytest.warns(expected_warning, func, *args, **kwargs) # или pytest.warns(expected_warning, "func(*args, **kwargs)")
Функция возвращает список всех выданных предупреждений (в виде объектов warnings.WarningMessage
), которые можно запросить для получения дополнительной информации:
with pytest.warns(RuntimeWarning) as record: warnings.warn("another warning", RuntimeWarning) # проверка на выдачу только одного предупреждения assert len(record) == 1 # проверка на соответствие сообщению assert record[0].message.args[0] == "another warning"
Кроме того, можно подробно изучить возникающие предупреждения, используя встроенную фикстуру recwarn
.
Встроенная фикстура recwarn
автоматически обеспечивает сброс фильтра предупреждений в конце теста, поэтому утечка глобального состояния не происходит.
recwarn
).Можно сохранять поднятые предупреждения в объект для подробного изучения либо с помощью pytest.warns()
, либо с помощью фикстуры recwarn
.
Сохранение поднятых предупреждений с помощью pytest.warns()
:
with pytest.warns() as record: warnings.warn("user", UserWarning) warnings.warn("runtime", RuntimeWarning) assert len(record) == 2 assert str(record[0].message) == "user" assert str(record[1].message) == "runtime"
Фикстура recwarn
будет записывать предупреждения для всей тестовой функции:
import warnings def test_hello(recwarn): warnings.warn("hello", UserWarning) assert len(recwarn) == 1 w = recwarn.pop(UserWarning) assert issubclass(w.category, UserWarning) assert str(w.message) == "hello" assert w.filename assert w.lineno
И recwarn
, и pytest.warns()
возвращают один и тот же интерфейс для записанных предупреждений: экземпляр WarningsRecorder
. Чтобы просмотреть записанные предупреждения, можно выполнить итерацию по этому экземпляру, для получения количества записанных предупреждений можно вызвать len()
или получить конкретное записанное предупреждение по индексу.
Еще несколько вариантов использования предупреждений, которые часто появляются в тестах, и предложения по их устранению:
Чтобы узнать, что выдается хотя бы одно предупреждение, используйте:
with pytest.warns(): ...
Чтобы узнать, что предупреждения не выдаются, используйте:
with warnings.catch_warnings(): warnings.simplefilter("error") ...
Для отключения предупреждений, используйте:
with warnings.catch_warnings(): warnings.simplefilter("ignore")
pytest
.Фреймворк pytest
может генерировать собственные предупреждения в некоторых ситуациях, таких как неправильное использование или устаревшие функции.
Например, pytest
выдаст предупреждение, если встретит класс, соответствующий python_classes
, но также определяющий конструктор __init__
, т.к. это предотвращает создание экземпляра класса:
# test_pytest_warnings.py class Test: def __init__(self): pass def test_foo(self): assert 1 == 1
Эти предупреждения могут быть отфильтрованы с использованием тех же встроенных механизмов, которые используются для фильтрации других типов предупреждений.