Сообщить об ошибке.

Пропуск тестов: skip() и skipif() с модулем pytest в Python

Пропуск тестов, которые не могут завершиться успешно

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

Декоратор pytest.mark.skip() означает, что тест ожидаемо пройдет только при соблюдении некоторых условий, в противном случае pytest должен вообще пропустить выполнение теста. Типичными примерами являются пропуск тестов только для Windows на платформах, отличных от Windows, или пропуск тестов, зависящих от внешнего ресурса, который в данный момент недоступен (например, базы данных).

Чтобы не загромождать вывод, подробная информация о пропущенных тестах по умолчанию не отображается. Чтобы увидеть детали, можно использовать опцию -r с "коротким" буквам, отображаемым в ходе теста, например, запуск: $ pytest -rs - означает, что нужно показать дополнительную информацию о skipped тестах.

Содержание:


Краткая сводка, как пропускать тесты.

  1. Безоговорочно пропускать все тесты в модуле:

    pytestmark = pytest.mark.skip("пропускаем все тесты в модуле")
    
  2. Пропустить все тесты в модуле на основе некоторого условия:

    pytestmark = pytest.mark.skipif(sys.platform == "win32", 
                        reason="тесты только для linux")
    
  3. Пропустить все тесты в модуле, если, например, нельзя импортировать модуль qrcode:

    pexpect = pytest.importorskip("qrcode")
    

Пропуск тестов: декоратор @pytest.mark.skip().

Декоратор/маркер @pytest.mark.skip(reason=None) безоговорочно пропускает тестовую функцию.

Самый простой способ пропустить тестовую функцию - это применить к ней декоратор @pytest.mark.skip, которому можно передать необязательную причину reason:

@pytest.mark.skip(reason="в настоящее время нет" 
                  "возможности протестировать")
def test_the_unknown():
    ...

Пропуск тестов: функция pytest.skip().

Функция skip(reason[, allow_module_level=False, msg=None]) пропускает выполнение теста с данным сообщением reason.

Эту функцию следует вызывать только во время тестирования (установка, вызов или демонтаж) или во время сбора тестов с использованием флага allow_module_level. Эту функцию можно вызывать и в doctests.

Принимаемые аргументы:

  • reason: (str) - сообщение, показывающее причину пропуска.
  • allow_module_level=False: (bool) - позволяет вызывать эту функцию на уровне модуля, пропуская остальную часть модуля.
  • msg: (str) - то же, что и reason, но устарело. Будет удалено в будущих версиях.

В качестве альтернативы, вызвав функцию pytest.skip(reason) можно принудительно пропустить тест во время выполнения или отладки:

def test_function():
    if not valid_config():
        pytest.skip("неподдерживаемая конфигурация")

Когда невозможно оценить условие пропуска во время импорта, полезен императивный метод.

Также можно пропустить весь модуль, применив функцию pytest.skip(reason, allow_module_level=True) на уровне модуля:

import sys
import pytest

if not sys.platform.startswith("win"):
    pytest.skip("пропуск тестов только для Windows", allow_module_level=True)

Примечание. Если нужно, чтобы тест будет пропущен при определенных условиях, таких как несоответствие платформ или зависимостей, то предпочтительнее явное объявление декоратора @pytest.mark.skip.

Пропуск тестов по условию @pytest.mark.skipif().

Декоратор/маркер @pytest.mark.skipif(condition, *, reason=None) пропускает тестовую функцию, если условие condition истинно.

Принимаемые аргументы.

  • condition: выражение, которое возвращает True/False или условие в виде строки, которая будет обрабатываться функцией eval().

    Например:

    @pytest.mark.skipif("not config.getvalue('db')")
    def test_function():
      pass
    

    Во время настройки тестовой функции строка с условием оценивается вызовом eval("not config.getvalue('db')", namespace). Пространство имен namespace содержит все глобальные переменные модуля и как минимум os и sys.

    Эквивалентом "логического условия” является:

    @pytest.mark.skipif(not pytest.config.getvalue("db"), 
                        reason="--db was not specified")
    def test_function():
      pass
    
  • reason=None: (str) - причина, по которой пропускается тестовая функция.

Если необходимо пропустить что-то условно, то можно использовать декоратор @pytest.mark.skipif(). Пример пропуска теста при запуске в интерпретаторе, предшествующем Python3.10:

import sys

@pytest.mark.skipif(sys.version_info < (3, 10), 
                    reason="требуется python3.10 или выше")
def test_function():
    ...

Если во время сбора тестов условие оценивается как True, то тестовая функция будет пропущена, а указанная причина появится в сводке при использовании опций $ pytest -rs.

Можно совместно использовать вызов pytest.mark.skipif() между модулями:

# test_mymodule.py
import mymodule

minversion = pytest.mark.skipif(
    mymodule.__versioninfo__ < (1, 1), 
    reason="требуется как минимум mymodule-1.1"
)

@minversion
def test_function():
    ...

Можно импортировать маркер и повторно использовать его в другом тестовом модуле:

# test_myothermodule.py
from test_mymodule import minversion

@minversion
def test_anotherfunction():
    ...

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

Пропуск всех тестовых функции класса или модуля.

Можно использовать декоратор @pytest.mark.skipif() (как и любой другой маркер) для классов:

@pytest.mark.skipif(sys.platform == "win32", 
                    reason="не запускается в Windows")
class TestPosixCalls:
    def test_function(self):
        """не будет настроен или запущен под платформой win32"""
        ...
...

Если условие sys.platform == "win32" истинно, то этот маркер выдаст результат пропуска для каждого из тестовых методов этого класса.

Если необходимо, то можно пропустить все тестовые функции модуля, для этого нужно использовать глобальную метку pytestmark:

# test_module.py
pytestmark = pytest.mark.skipif(...)

Если к тестовой функции применено несколько декораторов @pytest.mark.skipif(), то она будет пропущена, если какое-либо из условий этих декораторов имеет значение True.

Пропуск файлов или каталогов с тестами.

Иногда может потребоваться пропустить несколько файлов или каталог с тестами, например, если тесты основаны на функциях, специфичных для версии Python, или содержат код, который не надо запускать в pytest. В этом случае необходимо исключить эти файлы и каталоги во время сбора тестов при запуске. Дополнительные сведения смотрите раздел "Настройка собственного набора тестов" в материале "Как запускать/вызывать тесты pytest в Python".

Пропуск тестов, если невозможен импорт pytest.importorskip().

Функция pytest.importorskip(modname, minversion=None, reason=None) импортирует и возвращает запрошенный модуль modname или пропускает текущий тест, если модуль не может быть импортирован.

Принимаемые аргументы:

  • modname: (str) - имя модуля для импорта.
  • minversion (str) - если указано, то это атрибут __version__ импортированного модуля, должен быть не ниже указанной минимальной версии, иначе тест все равно будет пропущен.
  • reason: (str) - если указана, то это причина, когда модуль не может быть импортирован. Отображается в виде сообщения.

Можно пропустить тесты для отсутствующего импорта, используя функцию pytest.importorskip() на уровне модуля, в тесте или в функции настройки теста.

docutils = pytest.importorskip("docutils")

Код выше означает, что если нельзя импортировать docutils, то это приведет к пропуску результата теста. Также можно пропустить результат теста в зависимости от номера версии библиотеки:

docutils = pytest.importorskip("docutils", minversion="0.3")

Версия будет считана из атрибута __version__ указанного модуля.

Использование меток skip и skipif в параметрах к тесту.

Можно применять маркеры, такие как pytest.mark.skip() и pytest.mark.skipif() к отдельным экземплярам теста при использовании передаваемых к нему параметров:

import sys
import pytest

@pytest.mark.parametrize(
    ("n", "expected"),
    [
        (1, 2),
        pytest.param(
            0, 1, marks=pytest.mark.skip(reason="даст сбой")
        ),
        (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