代码覆盖最终阻止

Ale*_*yuk 10 java unit-testing emma try-catch

我有以下代码构造:

try {
   //some code
}
catch(CustomException custExc) {
  //log
}
catch(CustomException2 custExc2) {
  //log
}
catch(Exception exc) {
  //log
}
finally {
  //some code
} 
Run Code Online (Sandbox Code Playgroud)

我编写了单元测试:第一个是在没有抛出异常时执行的情况(仅执行try块代码,最后是块代码),另外3个是其中哪个是一次覆盖每个catch块(执行try块,catch之一)阻止,最后阻止).问题是Eclipse Emma插件显示我没有覆盖finally块.任何想法为什么会发生?

Tag*_*eev 8

在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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码有效地编译成如下代码:

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");
}
Run Code Online (Sandbox Code Playgroud)

这不是完全等效的代码,因为如果在System.out.println("In finally");(返回之前)期间发生新异常,则不会捕获它.然而,它显示了finally块在这里重复四次的粗略想法.如果你有几种退出try块的方法,特别是如果你有嵌套的try-finally块,它可以重复多次.还要注意<any exception>添加的特殊捕获量.即使您明确写入,它也会出现在字节码中catch(Throwable t).

由于像Emma或JaCoCo这样的代码覆盖工具在字节码级别上工作,他们并不知道这四个"In finally"printlns实际上是源代码中的相同语句.有可能执行字节码分析并非常可靠地确定字节码的哪些部分对应于单个源最终块(我实际上只编写了一次这样的分析器),但这不是一个非常简单的问题,并且有一些非平凡的警告.您还应该考虑到不同的编译器(例如,javac和ecj)会产生稍微不同的finally块布局.因此,似乎这项工作没有在流行的覆盖工具中完成,他们只是将不同的最终块副本视为不同的代码.

在您的特定情况下,似乎@bobbel是正确的:您没有测试未捕获的异常情况(该<any exception>捕获).


bob*_*bel 4

当我测试一些案例时,我发现,当抛出未捕获的异常时,您可能没有涵盖该案例。

给出以下示例:

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例将仅捕获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
}
Run Code Online (Sandbox Code Playgroud)

在我的另一个测试用例中,我有一个稍微不同的情况,具有一些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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这里else if没有捕获第二个分支,因为如果if and else if条件不为真怎么办?如果您在语句中提供除这两个值之外的其他值,它也会被捕获if-else-if