Spec-Zone.ru › pytest

Как использовать skip и xfail для тестов, которые не могут завершиться успешно

Вы можете пометить тестовые функции, которые нельзя запустить на определённых платформах или которые, как вы ожидаете, завершатся ошибкой. Так pytest сможет обработать их соответствующим образом и вывести сводку тестового сеанса, сохранив при этом зелёный статус набора тестов.

Skip означает, что тест должен пройти только при соблюдении определённых условий; в противном случае pytest должен пропустить его запуск. Распространённые примеры: пропуск тестов только для Windows на платформах, отличных от Windows, или тестов, зависящих от внешнего ресурса, который сейчас недоступен (например, базы данных).

Xfail означает, что вы ожидаете, что тест завершится ошибкой по какой-либо причине. Распространённый пример — тест для ещё не реализованной функции или неисправленной ошибки. Если тест проходит, несмотря на то что ожидалась ошибка (помечен с помощью pytest.mark.xfail), это xpass, и он будет указан в сводке тестов.

pytest отдельно подсчитывает и перечисляет тесты со статусами skip и xfail. Подробные сведения о пропущенных тестах и тестах со статусом xfail по умолчанию не отображаются, чтобы не загромождать вывод. Чтобы посмотреть подробности, соответствующие «коротким» буквам, отображаемым при выполнении тестов, можно использовать параметр -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. Вот пример пометки тестовой функции, которую нужно пропускать при запуске в интерпретаторе версии ниже Python 3.13:

import sys


@pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 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(): ...

Для больших наборов тестов обычно удобно определить маркеры в одном файле, а затем последовательно применять их во всём наборе тестов.

Вместо логических значений можно использовать строковые условия, но их неудобно использовать совместно в нескольких модулях, поэтому они поддерживаются главным образом для обратной совместимости.

Справка: 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__ указанного модуля.

Краткое описание

Краткое руководство по пропуску тестов в модуле в разных ситуациях:

  1. Безусловный пропуск всех тестов в модуле:
pytestmark = pytest.mark.skip("all tests still WIP")
  1. Пропуск всех тестов в модуле при выполнении некоторого условия:
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
  1. Пропуск всех тестов в модуле при отсутствии импортируемой зависимости:
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(): ...

Это особенно полезно для тестов со статусом xfail, которые приводят к аварийному завершению интерпретатора и требуют последующего исследования.

Параметр strict

По умолчанию ни XFAIL, ни XPASS не приводят к ошибке всего набора тестов. Это поведение можно изменить, задав параметру только для ключевого слова strict значение True:

@pytest.mark.xfail(strict=True)
def test_function(): ...

В этом случае результаты XPASS («неожиданно прошедшие») для этого теста будут приводить к ошибке всего набора тестов.

Значение параметра strict по умолчанию можно изменить с помощью параметра ini strict_xfail:

toml

[pytest]
xfail_strict = true

ini

[pytest]
strict_xfail = true

Игнорирование xfail

Указав в командной строке:

pytest --runxfail

можно принудительно запустить тест с маркером xfail и включить его в отчёт так, как будто этот маркер не был применён. В этом случае вызов pytest.xfail() также не даст никакого эффекта.

Примеры

Ниже приведён простой тестовый файл с несколькими вариантами использования:

from __future__ import annotations

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

При запуске с параметром отчёта о тестах 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 ============================

Skip/xfail с parametrize

При использовании parametrize можно применять маркеры, например 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

© 2015–2026 Holger Krekel and pytest-dev team
Licensed under the MIT License.
https://docs.pytest.org/en/stable/how-to/skipping.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API