Python Friday #53: Code Coverage for Pytest

Code coverage tools are a great help to find code that is not yet covered by your tests. Let us look how we can find those spots with pytest.

This post is part of my journey to learn Python. You can find the other parts of this series here.

 

Prerequisites

You need to install the packages coverage and pytest-coverage into your virtual environment:

 

Gilded Rose Kata as an example

Code coverage metrics depend on two things: code and tests. In this post I use the Python example of the Gilded Rose Kata. I like this kata as a starting point for its realistic code. This code was made so bad on purpose and what works here will work with your code base as well.

 

First steps

Let us start wit a new empty test file called test_gilded_rose.py next to the gilded_rose module and add this test code:

If we run pytest as in the posts before, it reports that our test passes. To get a coverage report on the command line, we need to run pytest with the --cov option:

=========== test session starts ============
platform win32 — Python 3.8.1, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\GildedRose-Refactoring-Kata\python
plugins: cov-2.10.1, html-2.1.1, metadata-1.10.0
collected 1 item

test_gilded_rose.py . [100%]

----------- coverage: platform win32, python 3.8.1-final-0 -----------
Name Stmts Miss Cover
-----------------------------------------
gilded_rose.py 36 17 53%
test_gilded_rose.py 7 0 100%
-----------------------------------------
TOTAL 43 17 60%

============ 1 passed in 0.10s =============

This report shows us that gilded_rose.py has 36 statements covered by our test, yet 17 are missing. This gives us a code coverage of 53%. Our test file with the one test got a coverage of 100%.

The more test files and modules you have, the bigger this report gets. You can limit the coverage report to a specific module by appending it to the cov option (--cov=your_module):

=========== test session starts ============
platform win32 — Python 3.8.1, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\GildedRose-Refactoring-Kata\python
plugins: cov-2.10.1, html-2.1.1, metadata-1.10.0
collected 1 item

test_gilded_rose.py . [100%]

----------- coverage: platform win32, python 3.8.1-final-0 -----------
Name Stmts Miss Cover
------------------------------------
gilded_rose.py 36 17 53%

============ 1 passed in 0.10s =============

This high-level overview is good to see the overall view, but it lacks the actionable details.

 

HTML report with line-by-line coverage

The coverage package comes with an HTML report that shows exactly what lines are covered and which are not. We can generate the report by adding the --cov-report html option:

=========== test session starts ============
platform win32 — Python 3.8.1, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
rootdir: D:\GildedRose-Refactoring-Kata\python
plugins: cov-2.10.1, html-2.1.1, metadata-1.10.0
collected 1 item

test_gilded_rose.py . [100%]

———– coverage: platform win32, python 3.8.1-final-0 ———–
Coverage HTML written to dir htmlcov
============ 1 passed in 0.10s =============

This creates a report in the htmlcov folder that looks like this:

The gilded_rose module coverage report

The thin green line between the line numbers and the code shows the covered lines, while the missing lines are marked in red. We now got a visual help to find the missing parts for which we need to write additional tests.

To change the report folder to myreport, you can replace html with html:myreport in the command line:

 

Branch coverage

The way we used the coverage tool so far has one caveat that you need to be aware of. It just looks at the lines of your code but ignores branches. If you have code that only has an if statement but not an else, you may not notice that you do not cover both possibilities. Let us use this code as an example to illustrate this problem:

If we create the coverage report as before, we expect the else part of method a to be missing:

The missing else statement is marked only in method a

As expected, the else part is marked red because it is missing. However, in method b we get no indication that we miss a test case for an odd number. We can change that with the option --cov-branch:

Now we get a yellow marker for partially covered branches

When we turn branch coverage on, we get a yellow marker on the missing branches. That allows us to find missing test cases when there is no else statement (as in function b).

 

Next

The coverage report is a great help to find missing test cases. While the details are great for developers, they are not that useful for other stakeholders. Next week I show you a tool to create an overview for your test suite.

1 thought on “Python Friday #53: Code Coverage for Pytest”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.