Прежде всего, вы звездочкой отметили, что print(1)
у вас показывает не покрытым, но у меня такая проблема не воспроизводится. Этот print
фактически в процессе выполнения теста выполняется, и в покрытии он учитывается.
Далее, по поводу абстрактного метода. Если вы тестируете только 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 / Advanced exclusion):
[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
Результат:
