I'm wondering whether measuring conditional code coverage by current tools for Java are not obsolete since Java 8 came up. With Java 8's Optional
and Stream
we can often avoid code branches/loops, which makes it easy to get very high conditional coverage without testing all possible execution paths. Let's compare old Java code with Java 8 code :
Before Java 8:
public String getName(User user) {
if (user != null) {
if (user.getName() != null) {
return user.getName();
}
}
return "unknown";
}
There are 3 possible execution paths in the above method. In order to get 100% of conditional coverage we need to create 3 unit tests.
Java 8:
public String getName(User user) {
return Optional.ofNullable(user)
.map(User::getName)
.orElse("unknown");
}
In this case, branches are hidden and we only need 1 test to get 100% coverage and it doesn't matter which case we will test. Though there are still the same 3 logical branches which should be covered I believe. I think that it makes conditional coverage statistics completely untrusted these days.
Does it make sense to measure conditional coverage for Java 8 code? Are there any other tools spotting undertested code?
1 Answer 1
Are there any tools that measure logical branches that can be created in Java 8?
I'm not aware of any. I tried running the code you have through JaCoCo (aka EclEmma) just to be sure, but it shows 0 branches in the Optional
version. I don't know of any method of configuring it to say otherwise. If you configured it to also include JDK files, it would theoretically show branches in Optional
, but I think it would be silly to start verifying JDK code. You just have to assume it's correct.
I think the core issue, though, is realizing that the additional branches you had prior to Java 8 were, in a sense, artificially created branches. That they no longer exist in Java 8 just means you now have the right tool for the job (in this case, Optional
). In the pre-Java 8 code you had to write extra unit tests so that you could have confidence that each branch of code behaves in an acceptable way - and this becomes a bit more important in sections of code that aren't trivial like the User
/getName
example.
In the Java 8 code, you're instead placing your confidence in the JDK that the code works properly. As is, you should treat that Optional
line just as code coverage tools treat it: 3 lines with 0 branches. That there are other lines and branches in the code below is something you just haven't paid attention to before, but has existed every time you've used something like an ArrayList
or HashMap
.
-
2"That they no longer exist in Java 8..." - I can't agree with it, Java 8 is backward compatible and
if
andnull
are still parts of the language ;-) It's still possible to write code in old way and to passnull
user or user withnull
name. Your tests should just prove that contract is met regardless of how method is implemented. The point is that there is no tool to tell you if you fully tested contract.Karol Lewandowski– Karol Lewandowski2016年12月01日 22:32:34 +00:00Commented Dec 1, 2016 at 22:32 -
1@KarolLewandowski I think what Shaz is saying is that if you trust how
Optional
(and related methods) work, you no longer have to test them. Not in the same way you tested anif-else
: everyif
was a potential minefield.Optional
and similar functional idioms are already coded and guaranteed not to trip you over, so essentially there's a "branch" that vanished.Andres F.– Andres F.2016年12月01日 23:22:01 +00:00Commented Dec 1, 2016 at 23:22 -
1@AndresF. I don't think Karol is suggesting that we test
Optional
. Like he said, logically we should still test thatgetName()
handles various possible inputs in the way we intend, regardless of its implementation. It's harder to determine this without code coverage tooling helping in the way it would pre-JDK8.Mike Partridge– Mike Partridge2016年12月02日 13:33:21 +00:00Commented Dec 2, 2016 at 13:33 -
1@MikePartridge Yes, but the point is that this isn't done via branch coverage. Branch coverage is needed when writing
if-else
because each of those constructs is completely ad-hoc. In contrast,Optional
,orElse
,map
, etc, are all already tested. The branches, in effect, "vanish" when you use more powerful idioms.Andres F.– Andres F.2016年12月02日 15:13:29 +00:00Commented Dec 2, 2016 at 15:13
Explore related questions
See similar questions with these tags.
getName
? It seems to be that ifuser
is null, it should return "unknown". Ifuser
is not null anduser.getName()
is null, it should return "unknown". Ifuser
is not null anduser.getName()
is not null, it should return that. So you would unit-test those three cases because that's what the contract ofgetName
is about. You seem to be doing it backward. You don't want to see the branches and write the tests according to those, you want to write your tests according to your contract, and ensure the contract is fullfilled. That's when you have good coverage.