问题描述
我有一个从 String 中提取寻址模式的 switch 语句,并且我已经编写了单元测试来涵盖,我认为这是所有可能发生的事情,但 JaCoCo 似乎跳过我的 switch 语句,导致覆盖率较低.
为什么,如果我的所有 case 语句(包括默认值)都在测试中执行,那么 switch 语句不会算作命中吗?
对于字符串切换
类乐趣{静态 int 乐趣(字符串 s){开关 (s) {案例我":返回 1;案例A":返回 2;案例Z":返回 3;案例ABS":返回 4;案例IND":返回 5;默认:返回 6;}}}
Oracle Java 编译器生成的字节码类似于以下代码(Eclipse Compiler for Java 生成的字节码略有不同)
int c = -1;开关(s.hashCode()){案例 65://+1 分支if (s.equals("I"))//+2 个分支c = 0;休息;案例 73://+1 分支if (s.equals("A"))//+2 个分支c = 1;休息;案例 90://+1 分支if (s.equals("Z"))//+2 个分支c = 2;休息;案例 64594://+1 分支if (s.equals("ABS"))//+2 个分支c = 3;休息;案例 72639://+1 分支if (s.equals("IND"))//+2 个分支c = 4;休息;默认值://+1 分支}开关 (c) {case 0://+1 分支返回 1;案例 1://+1 分支返回 2;案例 2://+1 分支返回 3;案例 3://+1 分支返回 4;案例 4://+1 分支返回 5;默认值://+1 分支返回 6;}
因此,具有 6 个 case 的原始 switch 语句在字节码中由 String 的 hashCode 的 6 个 case 加上 5 个 if 语句加上另一个具有 6 个 case 的 switch 表示案例.要查看此字节码,您可以使用 javap -c.
JaCoCo 执行字节码分析,并且在低于 0.8.0 的版本中没有按字符串切换的过滤器.您的测试涵盖 if 语句中的条件评估为 true 的情况,但不包括它们评估为 false 的情况.就我个人而言,我建议简单地忽略丢失的情况,因为目标不是测试编译器生成正确的代码,而是测试您的应用程序是否正确运行.但是为了这个答案的完整性 - 这里是涵盖所有字节码分支的测试:
import org.junit.Test;导入静态 org.junit.Assert.*;公共类 FunTest {@测试公共无效测试(){//原始字符串: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"));//相同的哈希码,但不同的字符串:assertEquals(6, Fun.fun("I"));assertEquals(6, Fun.fun("A"));assertEquals(6, Fun.fun("Z"));assertEquals(6, Fun.fun("ABS"));assertEquals(6, Fun.fun("IND"));//不同的哈希码来覆盖开关的默认情况assertEquals(6, Fun.fun(""));}}
JaCoCo 0.7.9 生成的报告作为证明:
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?
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("I")); assertEquals(6, Fun.fun("A")); assertEquals(6, Fun.fun("Z")); assertEquals(6, Fun.fun("ABS")); assertEquals(6, Fun.fun("IND")); // 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: