29

I have this switch system and I'm using eclemma to test the branch coverage. We are required to have at least 80% in branch coverage for everything so I'm trying to test as much as possible. However, eclemma tells me this switch system is not fully tested in terms of branch coverage.

pos = p.getCurrentPosition().substring(0, 1);
switch (pos) {
            case "G":
                goalkeepers++;
                break;
            case "D":
                defense++;
                break;
            case "M":
                midfield++;
                break;
            case "F":
                offense++;
                break;
            case "S":
                substitutes++;
                break;
            case "R":
                reserves++;
                break;
        }

I used straightforward JUnit tests to go trough each of these cases. Still eclemma marks this as yellow and says "7 of 19 branches missed". I would say there are only 7 ways to go through this switch system (the 6 individual cases+all undefined).

I tried searching for similar questions on stack overflow. Some of them had as solutions to use if/else for full coverage. I'm not sure if this is the only way possible to get this coverage.

Can anybody explain where all these 19 branches come from and how I could test these remaining 7 to get a 100% branch coverage on this switch case?

Sytze Andringa
  • 413
  • 1
  • 4
  • 12
  • Are there any chances the `pos` _variable_ could get other letter-values? If yes, add default case. – Voqus Jan 18 '15 at 19:19
  • I added the first line of code that tells how pos is defined. Adding an empty default case didn't change anything, as I'd expect – Sytze Andringa Jan 18 '15 at 19:25
  • I would advise not to test the other branches. Some constructs (see also Java 7s try with resource) generate much more complex byte code than the source code is. However you can be 100% (ok 99,99%) sure that the JRE does not make mistakes with this. So focus your test efforts elsewhere. – Thirler Mar 24 '16 at 12:04

2 Answers2

36

The Java compiler translates the switch-case code either to a tableswitch or to a lookupswitch. The tableswitch is used when there are only a few gaps are between the different cases. Otherwise, the lookupswitch is used.

In your case a tableswitch is used because the hash codes of your cases are closely spaced (unlike in the code referenced by owaism):

  16: tableswitch   { // 68 to 83
                68: 111 // 'D'
                69: 183
                70: 141 // 'F'
                71: 96  // 'G'
                72: 183
                73: 183
                74: 183
                75: 183
                76: 183
                77: 126 // 'M'
                78: 183
                79: 183
                80: 183
                81: 183
                82: 171 // 'R'
                83: 156 // 'S'
           default: 183
      }

The numbers to the left of the colon are the ordered hash codes and the filled gaps between them, the numbers to the right are the jump destinations. (In Java, the hash code of a character is its ASCII value.)

68 is the hash code of "D" (the lowest one), and 83 is the hash code of "S" (the highest one). 69 is the value of one of the gaps between the real cases and will jump to the default case.

However, I assume that EclEmma excludes these branches from the coverage computation of a tableswitch (it would lower the coverage even more because of the gaps). So we have 0 (counted) branches yet.

Next, an equals comparison of the string value is performed at each jump destination (except at the one of the default case). As your switch-case consists of 6 cases, we have 6 six jump destinations with an equals comparison.

The byte code of the comparison for case "G" is below:

  96: aload_3
  97: ldc           #10
  99: invokevirtual #11  java/lang/Object;)Z
 102: ifeq          183
 105: iconst_0
 106: istore        4
 108: goto          183
 111: aload_3

EclEmma counts two branches: either the input string and the case string are equals or they are not. Thus, we have 6 * 2 branches for the comparisons. (The default case does not branch.)

Next, if the two strings are equal the index of the case will be stored (byte code lines 105-106 for the case "G"). Then a jump to the second tableswitch will be executed. Otherwise, the jump will be executed directly.

 185: tableswitch   { // 0 to 5
                 0: 224
                 1: 237
                 2: 250
                 3: 263
                 4: 276
                 5: 289
           default: 299
      }

This switch operates on the previously stored case index and jumps to the code in the case (case "G" has index 0, the default case has -1). EclEmma counts 7 branches (6 cases plus the default case).

Consequently, we have 0 counted branches in the first tableswitch, 12 branches in the equals comparisons and further 7 branches in the second tableswitch. All in all, this results in 19 branches.


Your tests do not cover any of the 6 not-equals branches. In order to cover these, you would need to find a string for each case which is not equal to the case condition but has the same hash code. It is possible, but definitively not sensible...

Probably, the branch counting of EclEmma will be adjusted in the future.

Moreover, I guess you don't have a test case which does not match with any of the cases (thus the (implicit) default case is not covered.)

nrainer
  • 2,542
  • 2
  • 23
  • 35
  • 1
    Thanks for the nice explanation. Tells you why instrumenting byte code is the bad way to see test coverage of *source* code; the compiler necessarily "adds detail" in refining from the abstract (source code) to the implementation (byte code). That added detail is not part of the intension of the original application. – Ira Baxter Jan 18 '15 at 22:06
  • 1
    This SO answer is useful background on different instrumentation styles: stackoverflow.com/questions/15255798/what-are-the-differences-between-the-three-methods-of-code-coverage-analysis There are test coverage tools that compute coverage using only the source code; they would not have this this problem. Here are several: (our) Java Test Coverage tool (semanticdesigns.com/Products/TestCoverage) , clover (https://www.atlassian.com/software/clover/overview) and glenmccl.com/instr/index.htm – Ira Baxter Jan 18 '15 at 22:26
  • Thanks you took the time to explain this. Although it doesn't solve my problem, I now understand more why and how I could work around this. I'll try to edit my methods in such a way that I'll get my 80%. – Sytze Andringa Jan 18 '15 at 22:36
  • 1
    @IraBaxter This must simply be a bug in JaCoCo (the coverage engine used by EclEmma). My own code coverage tool (JMockit), which also does bytecode instrumentation, correctly identifies the seven paths for the code in the question; so, this is not at all a problem with *bytecode* instrumentation. Personally, I believe that *source* code instrumentation has serious disadvantages and is not the way to go. – Rogério Feb 14 '15 at 18:19
  • How can a bytecode instrumenter know that the compiler-added cases are not real? [Interested *why* you think source code instrumentation has "serious" disadvantages, rather than just hearing an assertion.] – Ira Baxter Feb 15 '15 at 21:47
  • @Rogério: No, EclEmma / JaCoCo does the bytecode instrumentation correctly. The point here is: It is not obvious from the source code that the corresponding byte code will contain 19 branches. – nrainer Feb 16 '15 at 19:04
  • @IraBaxter For starters, let me say I actually *tested* the code with JMockit Coverage, and it works beautifully (7 paths reported); you can try it. Now, the answer: if you look again at the bytecode, you will see that the tableswitch has 16 "cases" + 1 "default", *but only 7 different targets*. These targets are what the coverage tool needs to count as separate branches, *not* the 17 cases. So, you see, there is no need to examine the source code; the bytecode has all the information necessary. – Rogério Feb 16 '15 at 19:53
  • @IraBaxter Regarding the disadvantages of source instrumentation over on-the-fly bytecode instrumentation, I would say the following: 1) modifies the build process by adding source code generation before compilation - this is a huge inconvenience, IMO; 2) generates extra files on disk; 3) likely worse performance due to full source parsing and generation. Now, *offline* bytecode instrumentation (eg, Cobertura) also has issues 1 (for modified ".class" files) and 2, but more modern bytecode-based tools (eg, JaCoCo, JMockit, IntelliJ) do runtime instrumentation, with no extra files on disk. – Rogério Feb 16 '15 at 20:02
  • 1
    After a bit more testing, I discovered a mistake in my original testing the other day: I had pasted the code into an original Java 6 module, so I changed the switch-on-a-String to a regular switch. In that case, JMockit Coverage does count 7 paths, but not so on a Java 7 module with the original switch-on-String. This occurs for the reasons explained in this answer (the extra "ifeq" instructions and the final "tableswitch"), but only because the tool (just like JaCoCo) does not (yet) have explicit support for switchs on strings. I will be adding this for the next release, hopefully. – Rogério Feb 16 '15 at 20:39
0

Check out the following Link: http://sourceforge.net/p/eclemma/discussion/614869/thread/80e770df/

Below is snippet from the above link:

This for an example having a switch with 3 cases:

This is quite an interesting observation. From looking at the byte code one can see how the Java compiler handles the switch on strings. Actually it is an 3-step process:

  1. Switch on the hash code (3 Branches, 1 Default)
  2. For each hash code do an equals (3 * 2 branches)
  3. Do an final switch for the actual execution of the cases (3 Branches, 1 Default)

So we have an total of 14 branches which looks strange from the source code's point of view. What looks even more strange is that you're missing three of them. The explanation is step 2 where the equals method is applied additionally after the hash code. To cover these branches also you would need to find other strings with the same hash code. This is definitely something that might be filtered from coverage reports in future versions of JaCoCo:
https://sourceforge.net/apps/trac/eclemma/wiki/FilteringOptions

Community
  • 1
  • 1
owaism
  • 1,118
  • 1
  • 8
  • 21
  • I saw this when I googled the problem, but it was a bit unclear to me how I could just get those branches covered. Is it true that if the compiler indeed does 4 equals tests, and I need everything covered, I need to make tests that equal more than 2 cases? They are talking about the hash codes of strings. How can I use those to get this situation in the switch case in a rather easy way? – Sytze Andringa Jan 18 '15 at 20:23
  • @Gaargod: you'd have to figure for each switch statements, which additional bytecode entries got added for the compiler. I doubt you really want to do that, or that you can do that in any practical (e.g., modest time investment) way. You want the tool to report coverage for what's in your source code. It doesn't. Not a happy state. – Ira Baxter Jan 18 '15 at 22:22
  • see https://github.com/jacoco/jacoco/pull/496 a filtering option should be available in `0.7.10` – Julien Apr 12 '17 at 11:15