Excluding abstractproperties from coverage reports
Asked Answered
M

4

55

I have an abstract base class along the lines of:

class MyAbstractClass(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def myproperty(self): pass

But when I run nosetests (which coverage) on my project, it complains that the property def line is untested. It can't actually be tested (AFAIK) as instantiation of the abstract class will result in an exception being raised..

Are there any workarounds to this, or do I just have to accept < 100% test coverage?

Of course, I could remove the ABCMeta usage and simply have the base class raise NotImpementedError, but I prefer the former method.

Marjie answered 8/2, 2012 at 22:48 Comment(0)
F
44

There's no way to exclude the abstract properties precisely as you have it, but if you make a slight change, you can. Have your abstract property raise an error:

@abstractproperty
def myproperty(self): 
    raise NotImplementedError

Then you can instruct coverage.py to ignore lines that raise NotImplementedError. Create a .coveragerc file, and in it put:

[report]
exclude_lines =
    # Have to re-enable the standard pragma
    pragma: no cover

    # Don't complain if tests don't hit defensive assertion code:
    raise NotImplementedError

For more ideas about the kinds of lines you might want to always ignore, see: http://nedbatchelder.com/code/coverage/config.html

Firedog answered 9/2, 2012 at 14:12 Comment(7)
I ended up finding out about #pragma: no cover on IRC and went with that inline. I'm not a fan of having an implementation in an abstract property (even if it's just raise NotImplementedError as it seems to defeat the purpose).Marjie
wait: you're ok with putting a "#pragma: no cover" comment on every abstract property, but you aren't ok with changing the body from pass to "raise NotImplementedError"? To each his own, I guess... Glad you found a solution you like.Firedog
I definitely agree it's not an optimal solution either, but I prefer using inline directives like that to altering the intended use and benefit of the abstract base class module..Marjie
Another option is to add a docstring to your abstract methods or properties, instead of using pass. This has the added benefit of having documentation for how your abstract methods / properties are expected to behave.Shoifet
@WesleyBaugh: Sadly such commented empty methods are still reported by the coverage tool when you enable branch coverage (which should probably be enabled by default anyway).Xhosa
@Xhosa - The docstring solution worked fine for me, and I have branch coverage enabled. I'd say it's a bug if enabling branch coverage causes any changes to the behavior here given no branches are involved...Caudell
Note that the exclude_lines are regexes. Here's the relevant doc sectionTirol
S
71

For me the best solution was what @Wesley mentioned in his comment to the accepted answer, specifically replacing 'pass' with a docstring for the abstract property, e.g.:

class MyAbstractClass(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def myproperty(self):
       """ this property is too abstract to understand. """
Sister answered 9/10, 2013 at 15:11 Comment(0)
F
44

There's no way to exclude the abstract properties precisely as you have it, but if you make a slight change, you can. Have your abstract property raise an error:

@abstractproperty
def myproperty(self): 
    raise NotImplementedError

Then you can instruct coverage.py to ignore lines that raise NotImplementedError. Create a .coveragerc file, and in it put:

[report]
exclude_lines =
    # Have to re-enable the standard pragma
    pragma: no cover

    # Don't complain if tests don't hit defensive assertion code:
    raise NotImplementedError

For more ideas about the kinds of lines you might want to always ignore, see: http://nedbatchelder.com/code/coverage/config.html

Firedog answered 9/2, 2012 at 14:12 Comment(7)
I ended up finding out about #pragma: no cover on IRC and went with that inline. I'm not a fan of having an implementation in an abstract property (even if it's just raise NotImplementedError as it seems to defeat the purpose).Marjie
wait: you're ok with putting a "#pragma: no cover" comment on every abstract property, but you aren't ok with changing the body from pass to "raise NotImplementedError"? To each his own, I guess... Glad you found a solution you like.Firedog
I definitely agree it's not an optimal solution either, but I prefer using inline directives like that to altering the intended use and benefit of the abstract base class module..Marjie
Another option is to add a docstring to your abstract methods or properties, instead of using pass. This has the added benefit of having documentation for how your abstract methods / properties are expected to behave.Shoifet
@WesleyBaugh: Sadly such commented empty methods are still reported by the coverage tool when you enable branch coverage (which should probably be enabled by default anyway).Xhosa
@Xhosa - The docstring solution worked fine for me, and I have branch coverage enabled. I'd say it's a bug if enabling branch coverage causes any changes to the behavior here given no branches are involved...Caudell
Note that the exclude_lines are regexes. Here's the relevant doc sectionTirol
F
23

I have custom skip logic in my .coveragerc:

[report]
exclude_lines =
    pragma: no cover
    @abstract

This way all abstractmethods and abstractproperties are marked as skipped.

Fulmination answered 22/12, 2016 at 13:48 Comment(4)
Works well with tox !Zachariah
One little snag... It's possible for an @abstractmethod to have an implementation that a child can call. So, this could hide code that should be covered. (see note at the end of the documentation for abstractmethod)Feat
If you use import abc instead of from abc import abstract..., and since these lines are regex, you can use @abc\.abstract to exclude all abstract methods/properties.Tirol
@TimTisdall but should it? In this case it's no longer abstract, even if that is possible I would consider it bad practice.Fulmination
C
4

Straight from the docs. Add to your pyproject.toml the following section:

[tool.coverage.report]
exclude_also = [
    "raise AssertionError",
    "raise NotImplementedError",
    "@(abc\\.)?abstractmethod",
    ]

or to .coveragerc

[report]
exclude_also =
    raise AssertionError
    raise NotImplementedError
    @(abc\.)?abstractmethod
Chit answered 13/10, 2023 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.