Почему неправильно считается покрытие в коде с абстрактным классом / методом
Вот так выглядит код, который я хочу протестировать:
class AbstractWrapper(ABC):
@abstractmethod
def func(self) -> None:
raise NotImplementedError # *
class Wrapper(AbstractWrapper):
def func(self) -> None:
print(1) # *
Я написал pytest тесты и запустил команду pytest --cov --cov-report=html. Но почему-то в отчете покзаывает, что строки со * не покрыты тестами. Но тесты работают корректно и проверяют код (с неправильными входными данными тесты не проходят, с правильными - проходят).
Как это можно исправить, может я не ту команду использую?
Ответы (1 шт):
Прежде всего, вы звездочкой отметили что print(1) у вас показывает не покрытым, но у меня такая проблема не воспроизводится: если код фактически в процессе выполнения теста выполняется, то в покрытии он учитывается.
Далее, по поводу абстрактного метода. Если вы тестируете только Wrapper, то исходный не переопределенный метод останется не покрытым, т.к. фактически исходный метод не вызывается:
from abc import ABC, abstractmethod
class AbstractWrapper(ABC):
@abstractmethod
def func(self) -> None:
raise NotImplementedError
class Wrapper(AbstractWrapper):
def func(self) -> None:
print(1) # *
def test_wrapper(capsys):
wrapper = Wrapper()
wrapper.func()
captured = capsys.readouterr()
assert captured.out == "1\n"
В данном случае все пишу в одном файле test.py, тесты запускаю командой
pytest test.py --cov --cov-report=html
Покрытие:
Нужно или сделать метод не абстрактным, чтобы можно было отнаследоваться и вызвать исходный вариант метода (проверить, что он действительно кидает NotImplementedError):
from abc import ABC
import pytest
class AbstractWrapper(ABC):
def func(self) -> None:
raise NotImplementedError
...
def test_abstract_wrapper():
class Test(AbstractWrapper):
pass
wrapper = Test()
with pytest.raises(NotImplementedError):
wrapper.func()
Покрытие:
Либо добавляете комментарий # pragma: no cover абстрактному методу, тогда покрытие по нему не будет учитываться:
Также можно добавить декоратор абстрактного метода в exclude_also в конфиге .coveragerc (конфиг нужно положить в папку, откуда вызываете pytest) - это наиболее удобный вариант, на мой взгляд:
[report]
exclude_also =
@(abc\.)?abstractmethod
Полный пример из документации модуля coverage.py (Excluding code from coverage.py):
[report]
exclude_also =
def __repr__
if self.debug:
if settings.DEBUG
raise AssertionError
raise NotImplementedError
if 0:
if __name__ == .__main__.:
if TYPE_CHECKING:
class .*\bProtocol\):
@(abc\.)?abstractmethod
Результат:



