πŸ• Reading time: 3 minutes

Watching the code coverage percentage go up lets us sleep soundly. But is our system really tested, or only seemingly so?

πŸ“Œ Branch coverage

Let's look at the screenshot below, in particular the line where the isNotStringOrNotBlankString Predicate is invoked. We have true hits: 161, false hits: 0.

IntelliJ screenshot with the branch coverage window: the predicate isNotStringOrNotBlankString.test(value) shows true hits 161 and false hits 0 out of 333 total hits

What does that zero tell us? That, despite the 333 total passes, we never went through the branch where that predicate fails. This is usually a symptom of one of the following two things:

Branch coverage is the best "pruner" of useless code you can have!

πŸ“Œ The combinatorial trap

Let's get to the second point: testing only through integration tests is an attempt to solve a combinatorics problem with brute force.

Let's imagine a system with L layers and M methods, where each method has logical branches. If we try to test everything through integration, the combinations of inputs that have to traverse the various layers grow exponentially. Testing every possible scenario by trying every single path, always starting from the front door, is computationally unrealistic.

πŸ“Œ Unit tests are the "cheat code"

Hey, don't get me wrong, I'm not saying integration tests are useless: they're the ones that tell us the system really runs under realistic conditions. But it's the unit tests that validate the logic and tell us whether each little piece actually works. Ignoring branch coverage and relying only on end-to-end flows means looking at the system from afar, where everything apparently works, but taking for granted what happens inside.

See you at the next pill! β˜•