Java Jacoco 的代码覆盖率。合并从不同应用程序版本收集的可执行文件

Java code coverage with Jacoco. Merge exec files collected from different application versions

我正在尝试建立一个收集 QA 测试覆盖率并将此信息汇总到一份报告中的流程。我们有一个大团队,代码更改非常频繁,所以我的主要问题是无法从单一应用程序版本收集覆盖率。根据文档,Jococo 应该警告所有 类 执行数据不匹配的地方,并将它们报告为未涵盖。

[WARN] Execution data for class com/application/package/ClassName does not match.

相关文档:

然而,当我合并从不同版本的应用程序收集的 exec 文件时(总共数百万行代码和数千行更改),Jacoco 仅报告了大约四个 类 WARN,导致 12 行代码。用于报告的 JAR 文件取自作为合并一部分的最新版本。

所以,我只是想了解这是怎么可能的,我是否可以相信这份报告?

考虑以下示例。

版本 1 包含以下源文件:

src/Example.java

class Example {
  public static void main(String[] args) {
    C.print(A.getPrefix() + B.getSuffix());
  }
}

src/A.java

class A {
  static String getPrefix() {
    return "Hello, ";
  }
}

src/B.java

class B {
  static String getSuffix() {
    return "World";
  }
}

src/C.java

class C {
  static void print(String msg) {
    if ("Hello, World".equals(msg)) {
      System.out.println(msg + "!");
    } else {
      System.out.println(msg);
    }
  }
}

让我们编译并执行版本 1:

# javac src/A.java src/B.java src/C.java src/Example.java -d v1

# java \
    -javaagent:jacoco-0.8.4/lib/jacocoagent.jar=destfile=1.exec,sessionid=v1 \
    -cp v1 \
    Example
Hello, World!

版本 2:

src/Example.java修改

class Example {
  public static void main(String[] args) {
    C.print("Hello");
  }
}

src/A.java修改

class A {
  static String getPrefix() {
    return "";
  }
}

src/B.javasrc/C.java 未修改

让我们编译并执行版本 2:

# javac src/A.java src/B.java src/C.java src/Example.java -d v2

# java \ 
    -javaagent:jacoco-0.8.4/lib/jacocoagent.jar=destfile=2.exec,sessionid=v2 \
    -cp v2 \
    Example
Hello

注意Example.classA.class是不同的,而B.classC.class两个版本是一样的:

# diff --report-identical-files v1/Example.class v2/Example.class
Binary files v1/Example.class and v2/Example.class differ

# diff --report-identical-files v1/A.class v2/A.class
Binary files v1/A.class and v2/A.class differ

# diff --report-identical-files v1/B.class v2/B.class
Files v1/B.class and v2/B.class are identical

# diff --report-identical-files v1/C.class v2/C.class
Files v1/C.class and v2/C.class are identical

因此为这些 class 文件计算了 ID:

# java -jar jacoco-0.8.4/lib/jacococli.jar classinfo v1
  INST   BRAN   LINE   METH   CXTY   ELEMENT
     8      0      3      2      2   class 0xa170badd641f5a31 Example
     5      0      2      2      2   class 0x45b9146c94e31f23 B
     5      0      2      2      2   class 0xb8f01b5012761c26 A
    16      2      5      2      3   class 0xaf857eca353b9073 C

# java -jar jacoco-0.8.4/lib/jacococli.jar classinfo v2
  INST   BRAN   LINE   METH   CXTY   ELEMENT
     6      0      3      2      2   class 0x5915f0accdd77c81 Example
     5      0      2      2      2   class 0x45b9146c94e31f23 B
     5      0      2      2      2   class 0xa529ea9ab9745b77 A
    16      2      5      2      3   class 0xaf857eca353b9073 C

所以执行数据中记录的ids:

# java -jar jacoco-0.8.4/lib/jacococli.jar execinfo 1.exec
[INFO] Loading exec file 1.exec.
CLASS ID         HITS/PROBES   CLASS NAME
Session "v1": Fri Jul 05 20:39:50 CEST 2019 - Fri Jul 05 20:39:50 CEST 2019
b8f01b5012761c26    1 of   2   A
a170badd641f5a31    1 of   2   Example
45b9146c94e31f23    1 of   2   B
af857eca353b9073    3 of   5   C

# java -jar jacoco-0.8.4/lib/jacococli.jar execinfo 2.exec
[INFO] Loading exec file 2.exec.
CLASS ID         HITS/PROBES   CLASS NAME
Session "v2": Fri Jul 05 20:39:50 CEST 2019 - Fri Jul 05 20:39:50 CEST 2019
af857eca353b9073    2 of   5   C
5915f0accdd77c81    1 of   2   Example

让我们合并,合并 class 具有相同名称和相同 ID 的执行数据:

# java -jar jacoco-0.8.4/lib/jacococli.jar merge 1.exec 2.exec --destfile merged.exec
[INFO] Loading execution data file /private/tmp/j/1.exec.
[INFO] Loading execution data file /private/tmp/j/2.exec.
[INFO] Writing execution data to /private/tmp/j/merged.exec.

# java -jar jacoco-0.8.4/lib/jacococli.jar execinfo merged.exec
[INFO] Loading exec file merged.exec.
CLASS ID         HITS/PROBES   CLASS NAME
Session "v1": Fri Jul 05 20:39:50 CEST 2019 - Fri Jul 05 20:39:50 CEST 2019
Session "v2": Fri Jul 05 20:39:50 CEST 2019 - Fri Jul 05 20:39:50 CEST 2019
b8f01b5012761c26    1 of   2   A
a170badd641f5a31    1 of   2   Example
45b9146c94e31f23    1 of   2   B
af857eca353b9073    4 of   5   C
5915f0accdd77c81    1 of   2   Example

让我们使用合并的执行数据和版本 2 的 class 文件生成报告:

# java \
    -jar jacoco-0.8.4/lib/jacococli.jar \
    report merged.exec \
    --classfiles v2 \
    --sourcefiles src \
    --html report
[INFO] Loading execution data file /private/tmp/j/merged.exec.
[WARN] Some classes do not match with execution data.
[WARN] For report generation the same class files must be used as at runtime.
[WARN] Execution data for class A does not match.
[INFO] Analyzing 4 classes.

对于 src/Example.java 报告将显示有关版本 2 执行的数据,因为 v2/Example.class 的 ID 是 5915f0accdd77c81:

对于 src/A.java 报告不会显示任何内容,因为在 merged.exec 中没有数据对应于 v2/A.class 的 id,即 a529ea9ab9745b77:

生成报告时出现类似警告的消息

对于 src/B.java 报告将显示有关版本 1 执行的数据,因为在 merged.exec 中有来自 1.exec 的数据对应于 v2/B.class 的 ID - 45b9146c94e31f23:

对于 src/C.java 报告将显示关于两个版本执行的组合数据,因为在 merged.exec 中有来自 1.exec2.exec 的数据对应于 id v2/C.class - af857eca353b9073:

以上报告在某种意义上是正确的,因为它绝对正确地代表了关于为生成报告而提供的单个 class 文件的两次执行的合并:

  • v2/Example.class被执行
  • v2/A.class未执行
  • B.class 在版本 1
  • 中执行
  • C.class 中的两个分支都已执行 - 一个在版本 1 中,另一个在版本 2 中

不使用 class ids 报告将是绝对不正确的,无法检测到这一点

  • v2/A.class 将被视为已执行,而这从未发生过
  • A.java 中的第三行将显示为已执行,而这从未发生过

然而就所有classes一起以上报告并不代表最终版本,因为在最终版本

  • B.class永远不会被执行
  • 只会执行C.class中的一个分支

刚刚决定形象化之前的答案以便更好地理解(百分比数字是编造的,用于说明目的)。