19

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?

11
  • 5
    Coverage metrics have never been a good way to determine whether your code is well-tested, merely a way to determine what hasn't been tested. A good developer will think through the various cases in her mind, and devise tests for all of them -- or at least all that she thinks are important.
    – kdgregory
    Commented Dec 1, 2016 at 20:00
  • 3
    Of course high conditional coverage doesn't mean that we have good tests, but I think that it's HUGE advantage to know which execution paths are uncovered and this is what the question is mostly about. Without conditional coverage it's much harder to spot untested scenarios. Regarding paths: [user:null], [user:notnull, user.name:null], [user:notnull, user.name:notnull]. What I'm missing? Commented Dec 1, 2016 at 20:13
  • 6
    What's the contract of getName? It seems to be that if user is null, it should return "unknown". If user is not null and user.getName() is null, it should return "unknown". If user is not null and user.getName() is not null, it should return that. So you would unit-test those three cases because that's what the contract of getName 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. Commented Dec 1, 2016 at 20:29
  • 1
    Again, I'm not saying that coverage proves my code is perfectly tested, but it was EXTREMELY valuable tool showing me what I haven't tested for sure. I think that testing contracts is inseparable from testing execution paths (your example is exceptional as it involves implicit language mechanism). If you haven't tested path, then you haven't fully tested contract or contract is not fully defined. Commented Dec 1, 2016 at 21:42
  • 2
    I'm going to repeat my earlier point: that's always the case, unless you limit yourself only to basic language features and never call any function that hasn't been instrumented. Which means no third-party libraries, and no use of the SDK.
    – kdgregory
    Commented Dec 2, 2016 at 14:10

1 Answer 1

4

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.

4
  • 2
    "That they no longer exist in Java 8..." - I can't agree with it, Java 8 is backward compatible and if and null are still parts of the language ;-) It's still possible to write code in old way and to pass null user or user with null 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. Commented 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 an if-else: every if 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.
    Commented 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 that getName() 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. Commented 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.
    Commented Dec 2, 2016 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.