Как использовать skip и xfail для работы с тестами, которые не могут быть успешными¶
Вы можете пометить тестовые функции, которые не могут быть запущены на определенных платформах или которые, как вы ожидаете, не будут выполнены, чтобы pytest мог обработать их соответствующим образом и представить резюме тестовой сессии, сохраняя тестовый набор зеленым.
skip означает, что вы ожидаете, что ваш тест пройдет только при выполнении некоторых условий, в противном случае pytest должен вообще пропустить тест. Обычными примерами являются пропуск тестов только для windows на платформах, отличных от Windows, или пропуск тестов, которые зависят от внешнего ресурса, недоступного в данный момент (например, базы данных).
xfail означает, что вы ожидаете, что тест по какой-то причине не пройдет. Обычный пример - тест на еще не реализованную функцию или еще не исправленную ошибку. Если тест проходит, несмотря на то, что ожидалось, что он не пройдет (отмечено pytest.mark.xfail
), это xpass, и о нем будет сообщено в сводке тестов.
pytest
подсчитывает и перечисляет skip и xfail тесты отдельно. Подробная информация о пропущенных/xfailed тестах не показывается по умолчанию, чтобы не загромождать вывод. Вы можете использовать опцию -r
, чтобы увидеть подробности, соответствующие «коротким» буквам, показанным в прогрессе теста:
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
Более подробную информацию об опции -r
можно получить, выполнив pytest -h
.
(См. Опции встроенного файла конфигурации)
Пропуск тестовых функций¶
Самый простой способ пропустить тестовую функцию - пометить ее декоратором skip
, которому можно передать необязательный reason
:
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
...
Также можно пропустить императив во время выполнения или настройки теста, вызвав функцию pytest.skip(reason)
:
def test_function():
if not valid_config():
pytest.skip("unsupported configuration")
Императивный метод полезен, когда невозможно оценить состояние пропуска во время импорта.
Также можно пропустить весь модуль, используя pytest.skip(reason, allow_module_level=True)
на уровне модуля:
import sys
import pytest
if not sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
Ссылка: pytest.mark.skip
skipif
¶
Если вы хотите пропустить что-то условно, то вместо этого можно использовать skipif
. Вот пример пометки тестовой функции для пропуска при запуске на интерпретаторе, более раннем, чем Python3.10:
import sys
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function():
...
Если во время сбора условие оценивается как True
, тестовая функция будет пропущена, а при использовании -rs
в сводке появится указанная причина.
Вы можете совместно использовать маркеры skipif
между модулями. Рассмотрим этот тестовый модуль:
# content of test_mymodule.py
import mymodule
minversion = pytest.mark.skipif(
mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)
@minversion
def test_function():
...
Вы можете импортировать маркер и повторно использовать его в другом тестовом модуле:
# test_myothermodule.py
from test_mymodule import minversion
@minversion
def test_anotherfunction():
...
Для больших тестовых наборов обычно целесообразно иметь один файл, в котором определяются маркеры, которые затем последовательно применяются во всем тестовом наборе.
В качестве альтернативы вы можете использовать condition strings вместо булевых значений, но они не могут быть легко разделены между модулями, поэтому поддерживаются в основном из соображений обратной совместимости.
Ссылка: pytest.mark.skipif
Пропустить все тестовые функции класса или модуля¶
Вы можете использовать маркер skipif
(как и любой другой маркер) в классах:
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
class TestPosixCalls:
def test_function(self):
"will not be setup or run under 'win32' platform"
Если условие равно True
, этот маркер выдаст результат пропуска для каждого из тестовых методов данного класса.
Если вы хотите пропустить все тестовые функции модуля, вы можете использовать глобал pytestmark
:
# test_module.py
pytestmark = pytest.mark.skipif(...)
Если к тестовой функции применяется несколько декораторов skipif
, она будет пропущена, если любое из условий пропуска истинно.
Пропуск файлов или каталогов¶
Иногда вам может понадобиться пропустить целый файл или каталог, например, если тесты полагаются на специфические особенности версии Python или содержат код, который вы не хотите, чтобы pytest выполнял. В этом случае вы должны исключить файлы и каталоги из коллекции. Для получения дополнительной информации обратитесь к разделу Настройка сбора тестов.
Пропуск отсутствующей зависимости импорта¶
Вы можете пропустить тесты на отсутствующий импорт, используя pytest.importorskip на уровне модуля, внутри теста или функции настройки теста.
docutils = pytest.importorskip("docutils")
Если docutils
не может быть импортирован сюда, это приведет к пропуску теста. Вы также можете пропустить тест, основываясь на номере версии библиотеки:
docutils = pytest.importorskip("docutils", minversion="0.3")
Версия будет считана из атрибута __version__
указанного модуля.
Резюме¶
Вот краткое руководство о том, как пропускать тесты в модуле в различных ситуациях:
Безусловный пропуск всех тестов в модуле:
pytestmark = pytest.mark.skip("all tests still WIP")
Пропустить все тесты в модуле на основании некоторого условия:
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
Пропустить все тесты в модуле, если какой-то импорт отсутствует:
pexpect = pytest.importorskip("pexpect")
XFail: пометить функции теста как ожидаемые для отказа¶
Вы можете использовать маркер xfail
, чтобы указать, что вы ожидаете неудачу теста:
@pytest.mark.xfail
def test_function():
...
Этот тест будет запущен, но при неудаче не будет сообщено об отслеженном результате. Вместо этого в терминальных отчетах он будет указан в разделах «ожидается неудача» (XFAIL
) или «неожиданно пройден» (XPASS
).
Кроме того, можно пометить тест как XFAIL
из самого теста или его функции настройки в обязательном порядке:
def test_function():
if not valid_config():
pytest.xfail("failing configuration (but should work)")
def test_function2():
import slow_module
if slow_module.slow_function():
pytest.xfail("slow_module taking too long")
Эти два примера иллюстрируют ситуации, когда вы не хотите проверять условие на уровне модуля, то есть когда условие в противном случае оценивалось бы на наличие меток.
Это сделает test_function
XFAIL
. Обратите внимание, что после вызова pytest.xfail()
не выполняется никакой другой код, отличный от маркера. Это потому, что он реализован внутренне путем поднятия известного исключения.
Ссылка: pytest.mark.xfail
condition
параметр¶
Если ожидается, что тест не сработает только при определенном условии, вы можете передать это условие в качестве первого параметра:
@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function():
...
Обратите внимание, что вы должны передать также и причину (см. описание параметров в pytest.mark.xfail).
reason
параметр¶
Вы можете указать мотив ожидаемого сбоя с помощью параметра reason
:
@pytest.mark.xfail(reason="known parser issue")
def test_function():
...
raises
параметр¶
Если вы хотите уточнить причину неудачи теста, в аргументе raises
можно указать одно исключение или кортеж исключений.
@pytest.mark.xfail(raises=RuntimeError)
def test_function():
...
Тогда тест будет зарегистрирован как обычный сбой, если он не прошел с исключением, не упомянутым в raises
.
run
параметр¶
Если тест должен быть помечен как xfail и сообщен как таковой, но даже не должен выполняться, используйте параметр run
как False
:
@pytest.mark.xfail(run=False)
def test_function():
...
Это особенно полезно для тестов xfailing, которые приводят к краху интерпретатора и должны быть исследованы позже.
strict
параметр¶
По умолчанию и XFAIL
, и XPASS
не проваливают набор тестов. Вы можете изменить это, установив параметр strict
«только ключевое слово» в значение True
:
@pytest.mark.xfail(strict=True)
def test_function():
...
Это приведет к тому, что результаты XPASS
(«неожиданно пройден») этого теста не пройдут набор тестов.
Вы можете изменить значение параметра по умолчанию strict
с помощью параметра xfail_strict
ini:
[pytest]
xfail_strict=true
Игнорирование xfail¶
Указывая в командной строке:
pytest --runxfail
вы можете принудительно запустить и сообщить о тесте, помеченном xfail
, как если бы он вообще не был помечен. Это также приводит к тому, что pytest.xfail()
не производит никакого эффекта.
Примеры¶
Вот простой тестовый файл с несколькими вариантами использования:
import pytest
xfail = pytest.mark.xfail
@xfail
def test_hello():
assert 0
@xfail(run=False)
def test_hello2():
assert 0
@xfail("hasattr(os, 'sep')")
def test_hello3():
assert 0
@xfail(reason="bug 110")
def test_hello4():
assert 0
@xfail('pytest.__version__[0] != "17"')
def test_hello5():
assert 0
def test_hello6():
pytest.xfail("reason")
@xfail(raises=IndexError)
def test_hello7():
x = []
x[1] = 1
Запуск с опцией report-on-xfail дает следующий результат:
! pytest -rx xfail_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example
collected 7 items
xfail_demo.py xxxxxxx [100%]
========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
bug 110
XFAIL xfail_demo.py::test_hello5
condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
reason: reason
XFAIL xfail_demo.py::test_hello7
============================ 7 xfailed in 0.12s ============================
Пропуск/xfail с параметризацией¶
При использовании параметризации можно применять маркеры типа skip и xfail к отдельным экземплярам теста:
import sys
import pytest
@pytest.mark.parametrize(
("n", "expected"),
[
(1, 2),
pytest.param(1, 0, marks=pytest.mark.xfail),
pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
(2, 3),
(3, 4),
(4, 5),
pytest.param(
10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")
),
],
)
def test_increment(n, expected):
assert n + 1 == expected