40

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.

Ned Batchelder
  • 323,515
  • 67
  • 518
  • 625
Demian Brecht
  • 20,087
  • 3
  • 37
  • 44

3 Answers3

53

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. """
kouk
  • 1,213
  • 11
  • 12
37

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

Ned Batchelder
  • 323,515
  • 67
  • 518
  • 625
  • 1
    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). – Demian Brecht Feb 09 '12 at 20:50
  • 5
    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. – Ned Batchelder Feb 09 '12 at 21:06
  • 1
    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.. – Demian Brecht Feb 09 '12 at 21:53
  • 28
    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. – Wesley Baugh Mar 20 '13 at 23:20
  • @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). – ereOn Apr 29 '16 at 18:04
  • @ereOn - 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... – ArtOfWarfare Jan 31 '21 at 04:02
16

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.

Pax0r
  • 2,002
  • 2
  • 24
  • 44