我有以下代码构造:
try {
//some code
}
catch(CustomException custExc) {
//log
}
catch(CustomException2 custExc2) {
//log
}
catch(Exception exc) {
//log
}
finally {
//some code
}
我编写了单元测试:第一个涵盖了未抛出异常时的情况(仅执行try块代码和finally块代码),另外3个是立即覆盖每个catch块(执行try块,其中之一) catch 块和finally 块)。 问题是 Eclipse Emma 插件显示我没有覆盖finally 块。有什么想法为什么会发生吗?
在 Java 字节码中(至少从 Java 1.6 开始),
finally
块没有特殊的构造,因此它实际上被重复了很多次。例如,考虑以下方法:
public static void main(String[] args) {
try {
System.out.println("In try");
if(args.length > 0)
return;
System.out.println("No args");
}
catch(RuntimeException ex) {
System.out.println("In catch");
}
finally {
System.out.println("In finally");
}
}
这段代码被有效地编译成这样:
public static void main(String[] args) {
try {
System.out.println("In try");
if(args.length > 0) {
System.out.println("In finally");
return;
}
System.out.println("No args");
}
catch(RuntimeException ex) {
System.out.println("In catch");
System.out.println("In finally");
}
catch(<any exception> t) {
System.out.println("In finally");
throw t;
}
System.out.println("In finally");
}
这不是完全等效的代码,因为如果在
System.out.println("In finally");
期间(返回之前)发生新的异常,那么它将不会被捕获。然而,它显示了 finally
块在这里重复四次的粗略想法。如果您有多种方法退出 try 块,特别是如果您嵌套了 try-finally 块,则可以重复多次。另请注意添加的 <any exception>
特殊捕获。即使您明确编写 catch(Throwable t)
,它也会出现在字节码中。
由于像 Emma 或 JaCoCo 这样的代码覆盖工具在字节码级别上工作,他们不知道这四个
"In finally"
println 实际上是源代码中的相同语句。可以执行字节码分析并相当可靠地确定字节码的哪些部分对应于单个源finally块(我实际上写过一次这样的分析器),但这不是一个很容易的问题,并且有一些重要的警告。您还应该考虑到不同的编译器(例如 javac 和 ecj)会产生略有不同的 finally 块布局。所以看来这项工作并不是在流行的覆盖工具中完成的,他们只是将不同的最终块副本视为不同的代码。
在您的特定情况下,@bobbel 似乎是正确的:您没有测试未捕获的异常情况(即
<any exception>
捕获)。
当我测试一些案例时,我发现,当抛出未捕获的异常时,您可能没有涵盖该案例。
给出以下示例:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.junit.Test;
public class CodeCoverageFinallyTest {
@Test
public void testMyMethod() {
myMethod("2015-08-31");
myMethod("wrongFormat");
}
private void myMethod(final String source) {
try {
new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (final ParseException e) {
System.out.println("catch ParseException");
} finally {
System.out.println("finally");
}
}
}
这个例子只会捕获finally块中的两个分支之一,因为如果抛出未经检查的异常(即NullPointerException),您不会测试这种情况。
所以,如果你稍微改变你的测试用例,你将捕获finally块中的所有分支:
public void testMyMethod() {
myMethod("2015-08-31");
myMethod("wrongFormat");
myMethod(null); // also cover the case, that an unchecked and unhandled exception
// will be thrown
}
在我的另一个测试用例中,我的情况略有不同,带有一些
if-else-if
构造。
import org.junit.Test;
public class CodeCoverageIfElseTest {
@Test
public void testMyMethod() {
myMethod("2015-08-31");
myMethod("wrongFormat");
}
private void myMethod(final String source) {
if ("2015-08-31".equals(source)) {
System.out.println("Correct format");
} else if ("wrongFormat".equals(source)) {
System.out.println("Incorrect format");
}
}
}
这里
else if
没有捕获第二个分支,因为,如果 if
和 else if
条件不成立怎么办?如果您在 if-else-if
语句中提供除这两个值之外的其他值,它也会被捕获。
是的,当未捕获的投掷物抛出时,丢失的分支。
如果您对这个主题感到好奇,我建议您访问我的 github 页面,我在其中尝试了所有这些内容:https://github.com/bachoreczm/basicjava