Заставить охват учитывать только успешные тесты и игнорировать xfailing тесты

У меня есть несколько проектов, в которых я использую маркер pytest.mark.xfail, чтобы пометить тесты, которые завершились неудачей, но не должны завершаться ошибкой, чтобы можно было добавить неудачный тестовый пример до того, как проблема будет устранена. Я не хочу пропускать эти тесты, потому что если что-то, что я делаю, заставляет их начать проходить, я хочу быть проинформирован об этом, чтобы я мог удалить маркер xfail, чтобы избежать регрессии.

Проблема в том, что, поскольку xfail тесты на самом деле выполняются до тех пор, пока не завершатся сбоем, любые строки, которые привели к сбою, считаются "пройденными", даже если они являются частью непройденного теста, что дает мне вводящие в заблуждение показатели. о том, какая часть моего кода на самом деле проверена как работающая. Минимальный пример этого:

pkg.py

def f(fail):
    if fail:
        print("This line should not be covered")
        return "wrong answer"

    return "right answer"

test_pkg.py

import pytest
from pkg import f

def test_success():
    assert f(fail=False) == "right answer"

@pytest.mark.xfail
def test_failure():
    assert f(fail=True) == "right answer"

Запустив python -m pytest --cov=pkg, я получаю:

platform linux -- Python 3.7.1, pytest-3.10.0, py-1.7.0, pluggy-0.8.0
rootdir: /tmp/cov, inifile:
plugins: cov-2.6.0
collected 2 items

tests/test_pkg.py .x                                            [100%]

----------- coverage: platform linux, python 3.7.1-final-0 -----------
Name     Stmts   Miss  Cover
----------------------------
pkg.py       5      0   100%

Как видите, все пять строк покрыты, но строки 3 и 4 затрагиваются только во время теста xfail.

Сейчас я справляюсь с этим, настраивая tox для запуска чего-то вроде pytest -m "not xfail" --cov && pytest -m xfail, но в дополнение к тому, что это немного громоздко, это отфильтровывает только вещи с отметкой xfail, что означает, что условный xfails также отфильтровываются вне зависимости от того, выполняется условие или нет.

Есть ли способ сделать так, чтобы coverage или pytest не учитывали охват от неудачных тестов? В качестве альтернативы я бы согласился с механизмом игнорирования покрытия из xfail тестов, который игнорирует условные xfail тесты только в том случае, если условие выполнено.


person Paul    schedule 07.11.2018    source источник
comment
Это очень интересная идея! В покрытии 5.0 alpha мы можем отслеживать, какие тесты покрывали какие строки. Если мы доберемся до плагина pytest, который поможет с этим, возможно, он сможет отключить измерения вокруг тестов xfail.   -  person Ned Batchelder    schedule 08.11.2018
comment
Не могли бы вы написать об этом как о проблеме в репозитории cover.py? github.com/nedbat/coveragepy   -  person Ned Batchelder    schedule 08.11.2018
comment
@NedBatchelder Подойдет.   -  person Paul    schedule 09.11.2018


Ответы (1)


Поскольку вы используете подключаемый модуль pytest-cov, воспользуйтесь его no_cover маркер. При аннотации pytest.mark.no_cover покрытие кода будет отключено для теста. Осталось реализовать только применение маркера no_cover ко всем тестам, отмеченным pytest.mark.xfail. В вашем conftest.py:

import pytest

def pytest_collection_modifyitems(items):
    for item in items:
        if item.get_closest_marker('xfail'):
            item.add_marker(pytest.mark.no_cover)

Запуск вашего примера теперь даст:

$ pytest --cov=pkg -v
=================================== test session starts ===================================
platform darwin -- Python 3.7.1, pytest-3.9.1, py-1.7.0, pluggy-0.8.0
cachedir: .pytest_cache
rootdir: /Users/hoefling/projects/private/stackoverflow, inifile:
plugins: cov-2.6.0
collected 2 items

test_pkg.py::test_success PASSED                                                     [ 50%]
test_pkg.py::test_failure xfail                                                      [100%]

---------- coverage: platform darwin, python 3.7.1-final-0 -----------
Name     Stmts   Miss  Cover
----------------------------
pkg.py       5      2    60%


=========================== 1 passed, 1 xfailed in 0.04 seconds ===========================

Редактировать: работа с условием в маркере xfail

Доступ к аргументам маркера можно получить через marker.args и marker.kwargs, поэтому, если вы, например. иметь маркер

@pytest.mark.xfail(sys.platform == 'win32', reason='This fails on Windows')

получить доступ к аргументам с

marker = item.get_closest_marker('xfail')
condition = marker.args[0]
reason = marker.kwargs['reason']

Чтобы учесть флаг условия, хук сверху можно изменить следующим образом:

def pytest_collection_modifyitems(items):
    for item in items:
        marker = item.get_closest_marker('xfail')
        if marker and (not marker.args or marker.args[0]):
            item.add_marker(pytest.mark.no_cover)
person hoefling    schedule 07.11.2018
comment
Кажется, это половина решения; тесты с условным отказом также должны отключать покрытие только в том случае, если выполняется условие отказа. Я думаю, что это, вероятно, решает самую сложную часть проблемы. - person Paul; 08.11.2018
comment
о, вы хотите, чтобы покрытие было отключено для тестов с xfail результатом (но, например, оставлено включенным для тестов с xpass результатом)? Тип постобработки результатов покрытия после завершения теста? - person hoefling; 08.11.2018
comment
Нет, xfail принимает логическое значение, так что вы можете выражать такие вещи как сбои в Windows. Я всегда запускаю xfail со значением по умолчанию strict, но по моему опыту тесты получают xfail отметку независимо от того, истинно ли логическое значение. - person Paul; 08.11.2018
comment
Я понимаю что ты имеешь ввиду; флаг условия является первым атрибутом в аргументах маркера, поэтому его несложно учесть. Я обновил ответ примером. - person hoefling; 08.11.2018