Branch coverage measurement

In addition to the usual statement coverage, coverage.py also supports branchcoverage measurement. Where a line in your program could jump to more than onenext line, coverage.py tracks which of those destinations are actually visited,and flags lines that haven’t visited all of their possible destinations.

For example:

123456
defmy_partial_fn(x):# line 1ifx:#      2y=10#      3returny#      4my_partial_fn(1)

In this code, line 2 is anif statement which can go next to either line 3or line 4. Statement coverage would show all lines of the function as executed.But the if was never evaluated as false, so line 2 never jumps to line 4.

Branch coverage will flag this code as not fully covered because of the missingjump from line 2 to line 4. This is known as a partial branch.

How to measure branch coverage

To measure branch coverage, run coverage.py with the--branch flag:

coveragerun--branchmyprog.py

When you report on the results withcoveragereport orcoveragehtml,the percentage of branch possibilities taken will be included in the percentagecovered total for each file. The coverage percentage for a file is the actualexecutions divided by the execution opportunities. Each line in the file is anexecution opportunity, as is each branch destination.

The HTML report gives information about which lines had missing branches. Linesthat were missing some branches are shown in yellow, with an annotation at thefar right showing branch destination line numbers that were not exercised.

The XML report produced bycoveragexml also includes branch information,including separate statement and branch coverage percentages.

How it works

When measuring branches, coverage.py collects pairs of line numbers, a sourceand destination for each transition from one line to another. Static analysisof the source provides a list of possible transitions. Comparing the measuredto the possible indicates missing branches.

The idea of tracking how lines follow each other was fromTitus Brown.Thanks, Titus!

Excluding code

If you haveexcluded code, a conditional will not be countedas a branch if one of its choices is excluded:

1234567
defonly_one_choice(x):ifx:blah1()blah2()else:# pragma: no cover# x is always true.blah3()

Because theelse clause is excluded, theif only has one possible nextline, so it isn’t considered a branch at all.

Structurally partial branches

Sometimes branching constructs are used in unusual ways that don’t actuallybranch. For example:

whileTrue:ifcond:breakdo_something()

Here the while loop will never exit normally, so it doesn’t take both of its“possible” branches. For some of these constructs, such as “while True:” and“if 0:”, coverage.py understands what is going on. In these cases, the linewill not be marked as a partial branch.

But there are many ways in your own code to write intentionally partialbranches, and you don’t want coverage.py pestering you about them. You cantell coverage.py that you don’t want them flagged by marking them with apragma:

i=0whilei<999999999:# pragma: no branchifeventually():break

Here the while loop will never complete because the break will always be takenat some point. Coverage.py can’t work that out on its own, but the “no branch”pragma indicates that the branch is known to be partial, and the line is notflagged.