Do not report abstract methods as missing coverage
Summary
This feature requests especially concerns the term-missing report.
By their nature, methods decorated with abc.abstractmethod cannot be called directly.
In the example below, pytest-cov would consider pass to be an uncovered statement.
@abc.abstractmethod
def some_abstract_method():
pass
In this situation, it's impossible to reach 100% coverage.
I acknowledge that it's possible to use the raise NotImplementedError approach instead of @abc.abstractmethod.
However, I personally prefer to make use of abstract methods.
I would therefore greatly appreciate a feature that allows pytest-cov to omit abstract methods.
You might want to take a look at https://coverage.readthedocs.io/en/coverage-5.2.1/excluding.html
@ionelmc Thank you for your hint! Adding #pragma: no cover comment to the method definition causes the desired behavior.
Still, it feels somewhat redundant to annotate abstract methods like this.
Indeed I wonder if it's really right to report pass lines as no coverage at all. Certainly reporting an abstract method with a single pass line is overkill. It would be nice if this were fixed, rather than having to put excessive comments throughout.
For future readers, I've just realised this can easily by done by adding pass to exclude_lines in .coveragerc (described here and here):
[report]
exclude_lines =
# Skip any pass lines such as may be used for @abstractmethod
pass
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
I was just looking for a way to do this. I think it is worth mentioning that the statement in the original report
By their nature, methods decorated with abc.abstractmethod cannot be called directly.
is not entirely correct; abstract methods can be called from a child class. For example, if you run the following script it will output 2:
from abc import ABC, abstractmethod
class Abstract(ABC):
@abstractmethod
def mymethod(self):
return 1
class Concrete(Abstract):
def mymethod(self):
return super().mymethod() + 1
print(Concrete().mymethod())
Any option to ignore abstract methods would at least need a warning that it would ignore any with code in them. Ideally it would only ignore methods with no actual code.
Also, if you choose to add it to the list of excluded lines I would suggest using a full regular expression to make sure pass is the only code on the line. If you just add pass to the list then any line with the word pass anywhere in it (for example, num_passes += 1) will also be ignored. This includes any lines which have an inline comment with pass in it. The regular expression ^\s*pass\s*$ works for me: it requires the line to have only the word pass plus optional leading or trailing whitespace.
@bcbnz The note about adding "pass" being too broad is a good one, I will add that to the coverage.py docs.
There's something people are missing in this discussion: since coverage.py v4.1, if you exclude a decorator, it excludes the entire decorated function. So the best exclude line here would be:
[report]
exclude_lines =
@abstractmethod
@abc.abstractmethod
Once you do that, the entire abstract method is excluded from coverage, no matter what statements it contains.
BTW, this is also being discuss in coverage.py: https://github.com/nedbat/coveragepy/issues/1131
Another approach, which I now prefer, is to remove the pass altogether. It's not required if the method has a doc string.
Future readers may prefer to simply document their abstract methods rather than leaving them empty. So this can be changed:
class Foo(ABC):
@abstractmethod
def bar(self):
pass
To this:
class Foo(ABC):
@abstractmethod
def bar(self):
"""
Do xyz to this object
"""
Some may prefer to leave pass showing red in the coverage report as a gentle reminder to document.