Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python interface pattern and unit test code coverage

I'm working in a python project using unittest for testing along with coverage for code coverage.
I use the interface pattern extensively and I noticed that the overall code coverage percentage is heavily influenced by "non tested interfaces".
Consider the following:

class IReader(object):
    @abstractmethod
    def read(self):
        pass


class Reader(IReader):
    def read(self):
        # whatever

I test Reader but (obviously) I do not test IReader so the pass instruction is marked as not covered by tests.

Is there a way to ignore the interfaces from coverage?
Since this is one of my first python project, am I doing this totally wrong?

like image 789
Stefano Azzalini Avatar asked Feb 13 '17 08:02

Stefano Azzalini


People also ask

Is unit testing and code coverage same?

Unit tests help to ensure functionality and provide a means of verification for refactoring efforts. Code coverage is a measurement of the amount of code that is run by unit tests - either lines, branches, or methods.

How is UI test coverage measured?

Calculating test coverage is actually fairly easy. You can simply take the number of lines that are covered by a test (any kind of test, across your whole testing strategy) and divide by the total number of lines in your application.


1 Answers

I don't really see the point of having this read method defined with pass as single instruction. If you don't need it, let it raise NotImplementedError.

class IReader(object):
    @abstractmethod
    def read(self):
        raise NotImplementedError

As specified in the docs, an abstract method can have an implementation used by children with super(). But in your case, it does nothing. Therefore, unless you have a good reason, you might as well let it raise NotImplementedError.

(Arguably, a good reason could be a pattern where all your children call super().my_method() for some reason, so you need an implementation for all methods in the abstract class.)

 How to exclude the abstract method from coverage report 

Regardless, the test coverage is just an indicator you build: the part of the code you want to test that you actually test. Defining the "code you want to test" is up to you.

You could add a test to check that the abstract method returns NotImplementedError or just passes, if you see an interest in this.

Or you may think (which seems reasonable) that testing this is pointless, in which case excluding the method from coverage report #pragma: no cover seems like the way to go:

class IReader(object):  #pragma: no cover
    @abstractmethod
    def read(self):
        pass

The doc page linked above shows how you can also exclude all NotImplementedError methods adding this to your configuration file:

[report]
exclude_lines =
    pragma: no cover
    raise NotImplementedError

so that you don't have to add a pragma to each abstract method.


2020 edit

I just noticed the @abstractmethod decorator. Since it prevents the class from being instantiated, I wouldn't raise NotImplementedError. See this other answer.

I'd just leave the method empty. Syntaxically, a docstring is enough

class IReader(object):
    @abstractmethod
    def read(self):
        """Read data"""
like image 172
Jérôme Avatar answered Oct 19 '22 23:10

Jérôme