【问题标题】:Why is JaCoCo not covering my String switch statements?为什么 JaCoCo 没有涵盖我的 String switch 语句?
【发布时间】:2017-03-07 07:43:53
【问题描述】:

我有一个 switch 语句,它从 String 中提取寻址模式,并且我已经编写了单元测试来涵盖,我认为这是所有可能性,但 JaCoCo 似乎跳过了我的 switch 语句,导致较低覆盖范围。

为什么,如果我的所有case 语句(包括默认语句)都在测试中执行,switch 语句不会被算作命中吗?

【问题讨论】:

  • 这个问题可能是相关的:github.com/jacoco/jacoco/issues/116。还有它 4 岁的父母 (github.com/jacoco/jacoco/issues/15)
  • 另一种选择是,如果t[OP_ADD] 为空,则不测试 NPE。相关问题:stackoverflow.com/questions/35288022/…
  • 虽然我没有测试NullPointerException,但我正在测试ArrayOutOfBoundsIndexException,因为某些指令具有寻址模式,例如OP_LDA_I 虽然有些会立即得到解决,例如OP_SEC
  • 从技术上讲,t[OP_ADD] 可以为 null,在这种情况下,会发生 NullPointerException。据我所知,要涵盖这种情况,您需要为空值添加测试。
  • 哦,你是说我应该测试null。但这会出现在if 中,那为什么会影响我的switch 覆盖范围呢?

标签: java junit switch-statement jacoco codecov


【解决方案1】:

用于字符串切换

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 编译器生成的字节码类似于以下代码(Eclipse Compiler for Java 生成的字节码略有不同)

    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;
    }

因此,具有 6 个 case 的原始 switch 语句在字节码中由一个 switch 表示,该 switch 具有 6 个 case 的 hashCodeString 加上 5 个 if 语句加上另一个具有 6 个 case 的 switch。要查看此字节码,您可以使用javap -c

JaCoCo 执行字节码分析,并且在低于 0.8.0 的版本中没有按字符串切换的过滤器。您的测试涵盖了 if 语句中的条件评估为 true 的情况,但不包括它们评估为 false 的情况。就个人而言,我建议简单地忽略丢失的情况,因为目标不是测试编译器生成正确的代码,而是测试您的应用程序是否正确运行。但是为了这个答案的完整性 - 这里是涵盖所有字节码分支的测试:

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(""));
  }
}

JaCoCo 0.7.9 生成的报告作为证明:

JaCoCo version 0.8.0 provides filters,包括javac 为按字符串切换生成的字节码过滤器。因此即使没有额外的测试也会生成以下报告:

【讨论】:

  • 那么,找到Strings 与我所有的case 语句Strings 具有相同的hascode 还是咧嘴一笑? :( 谢谢
  • @RossDrew 我个人建议忽略丢失的案例
  • 我接受了这个答案,直到我编写了测试用例来涵盖它。我添加了测试,检查我的每个 switch 案例与 hashcode() 相同但 equals() 不同的案例。应该检查嵌套的if,但代码覆盖率保持不变。 github.com/rossdrew/emuRox/blob/master/src/test/groovy/com/rox/…
  • @RossDrew 添加调试输出 if (t[OP_ADD].startsWith("\0")) System.out.println("TESTED"); 就在开关告诉我您实际上并未测试相同哈希码的用例之前。您还可以在调试器的帮助下确认这一点。在您的测试中,\0 应该放在 IAZABSIND 之前,就像我的回答一样,而不是在 OP 之前,即 OP_ORA_\0ABS 等。
  • @BaptistePernet 请参阅 JSR-334 (jcp.org/aboutJava/communityprocess/final/jsr334/index.html) “为了保持字符串在 switch 中进行小的语言更改,Java SE 7 中的 JVM 查找开关和 tableswitch 指令不支持打开字符串。相反,Java 编译器负责将打开的字符串转换为具有适当语义的字节码指令序列。许多有效且高效的翻译方案是可能的,它们比连续比较打开的字符串与每个 case 标签具有更好的预期性能常数。”
猜你喜欢
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
  • 1970-01-01
  • 2015-04-20
  • 2016-09-05
  • 1970-01-01
相关资源
最近更新 更多