Why Your Code Coverage Might Not be as Good as It Seems

With the introduction of Optionals in Java 8, we can short-hand a lot of tedious and nested if-else statements.

For example:



Becomes:



Let's say we write a quick and simple unit test for this, like so:



We run the test with coverage enabled, and hey presto we have 100% coverage!



But wait a minute...

We didn't test all of the functionality in the method - what if object is null? What happened? Why do we still have 100% coverage?

Because of the way the Optional methods work, we have hit all the methods/lines, but we haven't checked whether the actual functionality is correct. This is because we have replaced our own if-else and delegated it to the method inside Optional, which essentially does that work for us.

Now you might argue that it's meaningless to test the functionality of internal libraries which are tried and tested; but from a functionality perspective, it makes sense to ensure that your code is doing what you'd expect it to, and cases are likely to be more complex than the example above.

If we reverted our code back to the original if-else version, and ran the test again, we wouldn't have full coverage:



So in the original scenario you might have picked up the missing test, but if you went with Optional, you might have missed it. This highlights two important points:

- should we be using Optional?
- if we are using Optional, how can we ensure that we cover the code?

I'll address these points separately.

Should we be using Optional?

I actually like using Optional, although I generally use it as a utility or shorthand for tedious or simple 'if-not-null-else-then' kind of situations. I don't like it being used as a parameter or return type because it just adds overhead in terms of boxing/unboxing everything with an Optional, but it certainly has its merits when used correctly. In any case, many of the Java 8 Stream methods will return Optionals, so it's almost unavoidable if you want to use streams. Once you get used to it, it's quite a handy tool to have at your disposal, so I definitely wouldn't discount it entirely.

So how do we cover our code while using Optional?

This is where TDD comes in handy - if you wrote a test to ensure that your code provided the required behaviour first, then we would automatically have a test which covered that scenario; even if it doesn't make a difference to the percentage of code covered (this is an important distinction - as we shouldn't be blindly chasing percentages - we should be testing that our code actually works).

If you're not using TDD, then it's simply a case of being careful; we have to spot where the utilities within the language might branch off and provide different behaviour in different scenarios, and ensure that, where relevant, the behaviour is tested properly.


Ultimately, the coverage percentage can never tell us whether all of the functionality is covered - simply the lines of code/branches. So don't always go chasing 100% unit test coverage - you might still be missing some key scenarios...



Comments