I have a switch
statement that extracts an addressing mode from a String
and I've written unit tests to cover, what I thought was every eventuality but JaCoCo seems to skip my switch
statements, resulting in lower coverage.
Why, if all my case
statements, including a default are being executed in tests, would the switch
statement not be counted as hit?
Yes, we can use a switch statement with Strings in Java.
JaCoCo mainly provides three important metrics: Lines coverage reflects the amount of code that has been exercised based on the number of Java byte code instructions called by the tests. Branches coverage shows the percent of exercised branches in the code, typically related to if/else and switch statements.
Return Inside switch Because a return ends execution of that method, there is no risk to fall through and hence no need for break .
Source code lines with exceptions show no coverage. Why? JaCoCo determines code execution with so called probes. Probes are inserted into the control flow at certain positions. Code is considered as executed when a subsequent probe has been executed.
Code coverage is a software metric used to measure how many lines of our code are executed during automated tests. In this tutorial, we're going to stroll through some practical aspects of using JaCoCo, a code coverage reports generator for Java projects. 2. Maven Configuration
In order to achieve 100% code coverage, we need to introduce tests that cover the missing parts shown in the initial report: Now we have enough tests to cover our the entire code, but to make sure of that, let's run the Maven command mvn jacoco:report to publish the coverage report:
In fail (Supplier) JaCoCo only sees a method invocation. Sorry, something went wrong. already existing improvement for the case of exceptions ( #310) does not cover this case unfortunately, since it marks lines above the line with call of method that throws exception. So I've been thinking about further improvements.
For the switch by String
class Fun {
static int fun(String s) {
switch (s) {
case "I":
return 1;
case "A":
return 2;
case "Z":
return 3;
case "ABS":
return 4;
case "IND":
return 5;
default:
return 6;
}
}
}
Oracle Java compiler generates bytecode similar to the following code (Eclipse Compiler for Java generates slightly different bytecode)
int c = -1;
switch (s.hashCode()) {
case 65: // +1 branch
if (s.equals("I")) // +2 branches
c = 0;
break;
case 73: // +1 branch
if (s.equals("A")) // +2 branches
c = 1;
break;
case 90: // +1 branch
if (s.equals("Z")) // +2 branches
c = 2;
break;
case 64594: // +1 branch
if (s.equals("ABS")) // +2 branches
c = 3;
break;
case 72639: // +1 branch
if (s.equals("IND")) // +2 branches
c = 4;
break;
default: // +1 branch
}
switch (c) {
case 0: // +1 branch
return 1;
case 1: // +1 branch
return 2;
case 2: // +1 branch
return 3;
case 3: // +1 branch
return 4;
case 4: // +1 branch
return 5;
default: // +1 branch
return 6;
}
So that original switch-statement with 6 cases is represented in bytecode by a switch with 6 cases for hashCode
of String
plus 5 if-statements plus another switch with 6 cases. To see this bytecode you can use javap -c
.
JaCoCo performs analysis of bytecode and in versions lower than 0.8.0 has no filter for switch by string. Your tests cover cases, where conditions in if-statements evaluate to true
, but not the cases where they evaluate to false
. Personally I would advise to simply ignore missing cases, because the goal is not to test that compiler generates proper code, but to test that your application behaves correctly. But for a sake of completeness of this answer - here is tests that cover all bytecode branches:
import org.junit.Test;
import static org.junit.Assert.*;
public class FunTest {
@Test
public void test() {
// original strings:
assertEquals(1, Fun.fun("I"));
assertEquals(2, Fun.fun("A"));
assertEquals(3, Fun.fun("Z"));
assertEquals(4, Fun.fun("ABS"));
assertEquals(5, Fun.fun("IND"));
// same hash codes, but different strings:
assertEquals(6, Fun.fun("\0I"));
assertEquals(6, Fun.fun("\0A"));
assertEquals(6, Fun.fun("\0Z"));
assertEquals(6, Fun.fun("\0ABS"));
assertEquals(6, Fun.fun("\0IND"));
// distinct hash code to cover default cases of switches
assertEquals(6, Fun.fun(""));
}
}
And report generated by JaCoCo 0.7.9 as a proof:
JaCoCo version 0.8.0 provides filters, including filter for bytecode that javac
produces for switch by string. And so generates following report even without additional tests:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With