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.
相关文档:
- https://www.eclemma.org/jacoco/trunk/doc/classids.html
- https://www.eclemma.org/jacoco/trunk/doc/flow.html
然而,当我合并从不同版本的应用程序收集的 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.java
和 src/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.class
和A.class
是不同的,而B.class
和C.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.exec
和 2.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
中的一个分支
刚刚决定形象化之前的答案以便更好地理解(百分比数字是编造的,用于说明目的)。
我正在尝试建立一个收集 QA 测试覆盖率并将此信息汇总到一份报告中的流程。我们有一个大团队,代码更改非常频繁,所以我的主要问题是无法从单一应用程序版本收集覆盖率。根据文档,Jococo 应该警告所有 类 执行数据不匹配的地方,并将它们报告为未涵盖。
[WARN] Execution data for class com/application/package/ClassName does not match.
相关文档:
- https://www.eclemma.org/jacoco/trunk/doc/classids.html
- https://www.eclemma.org/jacoco/trunk/doc/flow.html
然而,当我合并从不同版本的应用程序收集的 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.java
和 src/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.class
和A.class
是不同的,而B.class
和C.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.exec
和 2.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
中的一个分支
刚刚决定形象化之前的答案以便更好地理解(百分比数字是编造的,用于说明目的)。