4.5.15 · D2Software Engineering

Visual walkthrough — Code coverage — line, branch, path coverage

2,048 words9 min readBack to topic

This is a picture-first walkthrough. Read the parent topic first for the definitions; here we rebuild them slowly, one drawing at a time.


Step 1 — Code is a map of tiny rooms

WHAT. Before we count anything, we need to agree on what we are counting. Every program is a set of statements — instructions the computer performs one at a time. Think of each statement as a room you can stand in.

WHY. You cannot measure "how much got tested" until you decide what the units are. The word coverage literally means: of all the rooms that exist, how many did a visitor step into? So step one is drawing the rooms.

PICTURE. Below, our tiny program grade(x) is drawn as three rooms connected by corridors. A room is just one line of runnable code.

Figure — Code coverage — line, branch, path coverage

Our program has three executable lines (rooms): the test if x > 50, the return "pass", and the return "fail".


Step 2 — A visitor walks: line coverage is footprints

WHAT. Send one test in: grade(60). Follow where the visitor's feet land.

WHY. Line coverage asks the simplest possible question — "which rooms got a footprint?" We measure it because a room with zero footprints has zero evidence it works. It is the cheapest blind-spot detector we have.

PICTURE. The visitor enters the if room, the test is true for , so they walk to return "pass" and leave. The return "fail" room is never entered — it stays grey.

Figure — Code coverage — line, branch, path coverage

One test, one route — and already a room is untested. This is why one test rarely finishes the job.


Step 3 — Forks, not floors: branch coverage

WHAT. Zoom in on the if x > 50 room. It is not really one thing — it is a fork with two doors: the True door and the False door.

WHY. Line coverage only cares that you entered the fork room. But a fork's whole purpose is to send you two different ways. Branch coverage (also called decision coverage) counts the doors taken, not the rooms entered. We need it because a fork where nobody ever tried the second door hides half of the program's behaviour.

PICTURE. The same if from Step 2, now drawn as a fork with two labelled doors. grade(60) took only the green True door; the orange False door is untried.

Figure — Code coverage — line, branch, path coverage

Notice the punchline already visible in the numbers:

Branch coverage is stricter — it reports a lower, more honest number, because it refuses to be fooled by a fork whose second door was never opened.


Step 4 — Close the fork: why 100% branch drags line to 100%

WHAT. Add a second test grade(40). Now is false, so the visitor takes the orange False door into return "fail".

WHY. We want to see what happens the moment every door has been used. This step proves the parent note's theorem visually.

PICTURE. Both doors now glow, both return rooms are footprinted. Every room and every door is coloured.

Figure — Code coverage — line, branch, path coverage

This whole idea of "code as a map of rooms and forks" is formalised elsewhere as the Control Flow Graph; branch coverage is just "did we walk every edge of that graph?"


Step 5 — Two forks in a row: paths are whole journeys

WHAT. Now the harder program from the parent, two independent decisions in sequence:

def f(a, b):
    if a: x = 1     # fork D1
    else: x = 2
    if b: y = 1     # fork D2
    else: y = 2
    return x + y

WHY. With one fork, "every door" and "every journey" were the same thing. With two forks they split apart. A path is one complete start-to-finish journey — a specific choice of door at every fork. Path coverage asks: did we try every possible full journey?

PICTURE. A diamond map: fork D1 splits, both halves rejoin, then fork D2 splits again. Four complete routes run corner to corner: TT, TF, FT, FF.

Figure — Code coverage — line, branch, path coverage

Step 6 — Branch is cheap here, path is not

WHAT. Run just two tests: f(True, True) and f(False, False).

WHY. We want to expose the gap between branch and path — the exact place a bug can hide.

PICTURE. The TT route and the FF route are lit. Every single door has been used at least once (D1's True and False, D2's True and False). But the two crossing journeys — TF and FT — were never walked; they stay grey.

Figure — Code coverage — line, branch, path coverage

Step 7 — Path explosion, and the degenerate cases

WHAT. Generalise. What if there are independent forks in a row? What about loops? What about a program with no forks?

WHY. The parent warns path coverage grows "exponentially" and becomes "often infeasible." We show why, and we cover the boundary cases so no scenario surprises you.

PICTURE. A doubling tree: 1 fork → 2 leaves, 2 forks → 4, 3 forks → 8… the leaf count is the path count, exploding as .

Figure — Code coverage — line, branch, path coverage

The three boundary cases you must never be surprised by:

  • Zero forks (). Straight-line code, no decisions. Then : exactly one path. Here line, branch, and path coverage all mean the same thing, and one test hits of everything.
  • A loop. A loop that may run times adds another multiplying factor on top of — the number of feasible paths can become effectively infinite. This is why full path coverage is usually abandoned in practice.
  • Dependent forks. If if b can only be reached when a was true, some of the combinations are infeasible — they can never happen. Then the denominator (feasible paths) shrinks below . The formula is an upper bound; the honest denominator counts only routes that are actually reachable. The count of independent decisions is exactly what Cyclomatic Complexity measures.

The one-picture summary

Everything above, compressed: three concentric measuring cups. Lines are the widest, coarsest cup; branches sit inside; paths sit deepest. A test that fills the path cup necessarily fills the branch and line cups too — but not the reverse.

Figure — Code coverage — line, branch, path coverage
Recall Feynman retelling — the maze in plain words

Picture your code as a maze and your tests as people walking through it.

  • Rooms (lines): did somebody step in every room? One walker in grade(60) stepped in 2 of the 3 rooms — . The fail room stayed dark.
  • Doors (branches): at every fork, did someone push the left door and someone push the right door? The first walker only used one door of the one fork — . Adding a second walker who takes the other door lights both doors, and because opening every door means entering every room, the room-count jumps to for free. That is why branch is the stricter, more honest number.
  • Journeys (paths): with two forks, "every door used" is not "every full route walked." Two walkers (TT and FF) can touch all four doors yet skip the two crossing routes (TF, FT). A bug hiding on TF slips right past branch. Each extra fork doubles the routes, so a big maze has millions of journeys — walking them all is hopeless.
  • The boundaries: no forks = one route (everything is easy). A loop = endless routes (path coverage gives up). Some forks depend on earlier ones, so a few routes are impossible and don't count. And the eternal caveat: walking into a room proves you reached it, never that it is safe. To prove safety you must actually check the room — write an assert. " walked" is not " correct."
Recall

If a program has 4 independent sequential ifs, how many paths? ::: . Two tests give 100% branch but 50% path on two forks — what did they miss? ::: The two crossing combinations TF and FT. Why does 100% branch force 100% line? ::: Opening every door requires entering every room those doors lead to. Straight-line code with no decisions has how many paths? ::: Exactly one (). Order the three metrics from most permissive percentage to strictest for a fixed suite. ::: Line ≥ Branch ≥ Path.