Python Coverage boolean compound condition line exit missing branch coverage
With branch coverage
enabled in Coverage.py
or pytest-coverage
, you might see it report missing
coverage on a compound boolean condition, like this:
def foobar() -> bool:
return (
condition_1()
and condition_2()
and condition_3()
and condition_4()
and condition_5()
)
That might lead to a report that the line where the boolean compound condition begins to the exit of the function are missing coverage. The report will look like this:
---------- coverage: platform linux, python 3.12.1-final-0 -------
Name Stmts Miss Branch BrPart Cover Missing
------------------------------------------------------------------
src/foobar.py 39 0 16 1 98% 37->exit
------------------------------------------------------------------
TOTAL 188 0 68 1 99%
The “line to exit” part is 37->exit
in the Missing
column on the right.
This can be surprising if you know you have tests covering that compound boolean
evaluating to True and False. This issue is caused by the branch coverage
analysis in Coverage.py
being quite thorough. It requires that each possible
step through the compound boolean is covered.
This isn’t as bad as it sounds if you thought that meant you need to write tests for a Cartesian product of all of the possible permutations of conditions in the compound. You only need to cover each boolean step sequentially. For example, with a boolean compound with five sub-conditions, you’d need tests covering those sub-conditions in the following sequences:
False (overall False)
True, False (overall False)
True, True, False (overall False)
True, True, True, False (overall False)
True, True, True, True, True (overall True)
Note that only one of the combinations evaluates to True overall.
When writing tests for this kind of boolean compound, it’s common to only test
it evaluating to overall True, and perhaps one of the possible False
evaluations, which is what leads Coverage.py
to report missing branch
coverage. Unfortunately Coverage.py
will not tell you which step of the
compound boolean is missing coverage, so the final report of [line]->exit
is a
bit vague and difficult to decipher.
Making sure that tests cover all of the possible paths through the compound
boolean will ensure that Coverage.py
reports 100% test coverage for it.