8个分支机构尝试使用资源 – 可能覆盖雅可比?

我有一些使用资源尝试的代码,在jacoco它只是覆盖了一半。 所有的源代码行都是绿色的,但是我得到一个黄色的小符号,告诉我只有8个分支中的4个被覆盖。

在这里输入图像说明

我很难搞清楚所有分支是什么,以及如何编写覆盖它们的代码。 三个可能的地方抛出PipelineException 。 这些是createStageList()processItem()和隐含的close()

  1. 不抛出任何例外,
  2. createStageList()引发exception
  3. processItem()引发exception
  4. 抛出close()的exception
  5. processItem()close()引发exception

我想不出任何其他情况,但我仍然只有8个中的4个被覆盖。

有人可以向我解释为什么它是8的4,并有无论如何击中所有8个分支? 我不擅长decyrpting /阅读/解释字节码,但也许你是… :)我已经看到https://github.com/jacoco/jacoco/issues/82 ,但既不是它也不是问题它引用非常多的帮助(除了注意到这是由于编译器生成的块)

嗯,正如我写完这篇文章,我想到了什么情况下可能不会被我上面提到的testing…我会发布一个答案,如果我说得对。 我相信这个问题,这个答案在任何情况下都会帮助别人。

编辑:不,我没有find它。 抛出RuntimeExceptions(不由catch块处理)不包括更多的分支

那么我不能告诉你究竟是什么问题,但我可以告诉你如何尝试使用资源编译。 基本上,有很多编译器生成的开关来处理在各个点抛出的exception。

如果我们采取以下代码并编译它

 public static void main(String[] args){ String a = "before"; try (CharArrayWriter br = new CharArrayWriter()) { br.writeTo(null); } catch (IOException e){ System.out.println(e.getMessage()); } String a2 = "after"; } 

然后拆卸,我们得到

 .method static public main : ([Ljava/lang/String;)V .limit stack 2 .limit locals 7 .catch java/lang/Throwable from L26 to L30 using L33 .catch java/lang/Throwable from L13 to L18 using L51 .catch [0] from L13 to L18 using L59 .catch java/lang/Throwable from L69 to L73 using L76 .catch [0] from L51 to L61 using L59 .catch java/io/IOException from L3 to L94 using L97 ldc 'before' astore_1 L3: new java/io/CharArrayWriter dup invokespecial java/io/CharArrayWriter <init> ()V astore_2 aconst_null astore_3 L13: aload_2 aconst_null invokevirtual java/io/CharArrayWriter writeTo (Ljava/io/Writer;)V L18: aload_2 ifnull L94 aload_3 ifnull L44 L26: aload_2 invokevirtual java/io/CharArrayWriter close ()V L30: goto L94 L33: .stack full locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable stack Object java/lang/Throwable .end stack astore 4 aload_3 aload 4 invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V goto L94 L44: .stack same aload_2 invokevirtual java/io/CharArrayWriter close ()V goto L94 L51: .stack same_locals_1_stack_item stack Object java/lang/Throwable .end stack astore 4 aload 4 astore_3 aload 4 athrow L59: .stack same_locals_1_stack_item stack Object java/lang/Throwable .end stack astore 5 L61: aload_2 ifnull L91 aload_3 ifnull L87 L69: aload_2 invokevirtual java/io/CharArrayWriter close ()V L73: goto L91 L76: .stack full locals Object [Ljava/lang/String; Object java/lang/String Object java/io/CharArrayWriter Object java/lang/Throwable Top Object java/lang/Throwable stack Object java/lang/Throwable .end stack astore 6 aload_3 aload 6 invokevirtual java/lang/Throwable addSuppressed (Ljava/lang/Throwable;)V goto L91 L87: .stack same aload_2 invokevirtual java/io/CharArrayWriter close ()V L91: .stack same aload 5 athrow L94: .stack full locals Object [Ljava/lang/String; Object java/lang/String stack .end stack goto L108 L97: .stack same_locals_1_stack_item stack Object java/io/IOException .end stack astore_2 getstatic java/lang/System out Ljava/io/PrintStream; aload_2 invokevirtual java/io/IOException getMessage ()Ljava/lang/String; invokevirtual java/io/PrintStream println (Ljava/lang/String;)V L108: .stack same ldc 'after' astore_2 return .end method 

对于那些不讲字节码的人来说,这大致相当于下面的伪Java。 我不得不使用gotos,因为字节码并不真正对应于Java控制stream。

正如你所看到的,有很多情况可以处理抑制exception的各种可能性。 能够涵盖所有这些情况是不合理的。 实际上,第一个try块的goto L59分支是不可能达到的,因为第一个catch Throwable将捕获所有exception。

 try{ CharArrayWriter br = new CharArrayWriter(); Throwable x = null; try{ br.writeTo(null); } catch (Throwable t) {goto L51;} catch (Throwable t) {goto L59;} if (br != null) { if (x != null) { try{ br.close(); } catch (Throwable t) { x.addSuppressed(t); } } else {br.close();} } break; try{ L51: x = t; throw t; L59: Throwable t2 = t; } catch (Throwable t) {goto L59;} if (br != null) { if (x != null) { try{ br.close(); } catch (Throwable t){ x.addSuppressed(t); } } else {br.close();} } throw t2; } catch (IOException e) { System.out.println(e) } 

在这里输入图像说明

我可以覆盖全部8个分支,所以我的答案是YES。 看看下面的代码,这只是一个快速的尝试,但它的工作原理(或者看看我的github: https : //github.com/bachoreczm/basicjava和'trywithresources'包,你可以find,如何试用,资源工程,请参阅“ExplanationOfTryWithResources”类):

 import java.io.ByteArrayInputStream; import java.io.IOException; import org.junit.Test; public class TestAutoClosable { private boolean isIsNull = false; private boolean logicThrowsEx = false; private boolean closeThrowsEx = false; private boolean getIsThrowsEx = false; private void autoClose() throws Throwable { try (AutoCloseable is = getIs()) { doSomething(); } catch (Throwable t) { System.err.println(t); } } @Test public void test() throws Throwable { try { getIsThrowsEx = true; autoClose(); } catch (Throwable ex) { getIsThrowsEx = false; } } @Test public void everythingOk() throws Throwable { autoClose(); } @Test public void logicThrowsException() { try { logicThrowsEx = true; everythingOk(); } catch (Throwable ex) { logicThrowsEx = false; } } @Test public void isIsNull() throws Throwable { isIsNull = true; everythingOk(); isIsNull = false; } @Test public void closeThrow() { try { closeThrowsEx = true; logicThrowsEx = true; everythingOk(); closeThrowsEx = false; } catch (Throwable ex) { } } @Test public void test2() throws Throwable { try { isIsNull = true; logicThrowsEx = true; everythingOk(); } catch (Throwable ex) { isIsNull = false; logicThrowsEx = false; } } private void doSomething() throws IOException { if (logicThrowsEx) { throw new IOException(); } } private AutoCloseable getIs() throws IOException { if (getIsThrowsEx) { throw new IOException(); } if (closeThrowsEx) { return new ByteArrayInputStream("".getBytes()) { @Override public void close() throws IOException { throw new IOException(); } }; } if (!isIsNull) { return new ByteArrayInputStream("".getBytes()); } return null; } } 

没有真正的问题,但想要在那里投入更多的研究。 tl; dr =看起来你可以达到try-finally的100%覆盖率,但不能用于试用资源。

可以理解的是,老派的try-finally和Java7试用资源是有区别的。 这里有两个等价的例子,展示了使用替代方法的相同事物。

老学校的例子(一个尝试 – 最后的方法):

 final Statement stmt = conn.createStatement(); try { foo(); if (stmt != null) { stmt.execute("SELECT 1"); } } finally { if (stmt != null) stmt.close(); } 

Java7示例(试用资源方法):

 try (final Statement stmt = conn.createStatement()) { foo(); if (stmt != null) { stmt.execute("SELECT 1"); } } 

分析:老派的例子:
使用Jacoco 0.7.4.201502262128和JDK 1.8.0_45,使用以下4个testing,我能够在Old School示例中获得100%的线路,指令和分支机构覆盖率:

  • 基本润滑脂path(声明不为null,execute()正常运行)
  • execute()抛出exception
  • foo()抛出exceptionAND语句返回为null
  • 语句返回为null

Jacoco在'try'(空检查)内指示2个分支,在finally(在空检查)内指示4个分支。 所有被完全覆盖。

分析:java-7例子:
如果同样的4个testing是针对Java7风格的例子运行的,jacoco表示6/8分支被覆盖(在try上)和2/2的try-try中的null检查。 我尝试了一些额外的testing来提高覆盖率,但是我找不到比6/8更好的方法。 正如其他人所指出的那样,java-7例子中的反编译代码(我也看过)build议java编译器正在为资源尝试生成无法访问的段。 Jacoco正在报告(准确)这样的细分市场存在。

更新:使用Java7编码风格,您可以使用Java7 JRE获得100%的覆盖率(请参阅下面的Matyas响应)。 但是,使用Java8 JRE的Java7编码风格,我相信你会碰到覆盖的6/8分支。 相同的代码,只是不同的JRE。 似乎字节代码在两个JRE之间以不同方式创build,Java8创build不可达path。

我有这样的一个类似的问题:

 try { ... } finally { if (a && b) { ... } } 

它抱怨8个分支中有2个没有被覆盖。 最终这样做:

 try { ... } finally { ab(a,b); } void ab(a, b) { if (a && b) { ... } } 

没有其他的变化,我现在达到100%….

四岁,但仍然…

  1. 使用非空AutoCloseable快乐path
  2. 快乐path与空AutoCloseable
  3. 抛出写
  4. 抛出closures
  5. 抛出并closures
  6. 抛出资源规范( 部分,例如构造函数调用)
  7. try块中抛出,但AutoCloseable为null

以上列出了所有7个条件 – 8个分支的原因是由于重复条件。

所有分支都可以到达, try-with-resources是相当简单的编译器糖(至less与switch-on-string相比) – 如果无法到达,那么它就是一个编译器错误。

实际上只需要6个unit testing(在下面的例子中, throwsOnClose@Ingore d,分支覆盖是8/8。

另外请注意, Throwable.addSuppressed(Throwable)不能抑制自己,所以生成的字节码包含一个额外的保护(IF_ACMPEQ – 引用相等),以防止这一点)。 幸运的是,这个分支由抛出,抛出closures和抛出 – 写入 – closures情况所覆盖,因为字节码可变槽被3个外部2个exception处理器区域重用。

这与Jacoco 不是一个问题 – 实际上,链接问题#82中的示例代码是不正确的,因为没有重复的空检查,并且closures周围没有嵌套的catch块。

JUnittesting演示了8个分支中的8个覆盖

 import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import org.junit.Ignore; import org.junit.Test; public class FullBranchCoverageOnTryWithResourcesTest { private static class DummyOutputStream extends OutputStream { private final IOException thrownOnWrite; private final IOException thrownOnClose; public DummyOutputStream(IOException thrownOnWrite, IOException thrownOnClose) { this.thrownOnWrite = thrownOnWrite; this.thrownOnClose = thrownOnClose; } @Override public void write(int b) throws IOException { if(thrownOnWrite != null) { throw thrownOnWrite; } } @Override public void close() throws IOException { if(thrownOnClose != null) { throw thrownOnClose; } } } private static class Subject { private OutputStream closeable; private IOException exception; public Subject(OutputStream closeable) { this.closeable = closeable; } public Subject(IOException exception) { this.exception = exception; } public void scrutinize(String text) { try(OutputStream closeable = create()) { process(closeable); } catch(IOException e) { throw new UncheckedIOException(e); } } protected void process(OutputStream closeable) throws IOException { if(closeable != null) { closeable.write(1); } } protected OutputStream create() throws IOException { if(exception != null) { throw exception; } return closeable; } } private final IOException onWrite = new IOException("Two writes don't make a left"); private final IOException onClose = new IOException("Sorry Dave, we're open 24/7"); /** * Covers one branch */ @Test public void happyPath() { Subject subject = new Subject(new DummyOutputStream(null, null)); subject.scrutinize("text"); } /** * Covers one branch */ @Test public void happyPathWithNullCloseable() { Subject subject = new Subject((OutputStream) null); subject.scrutinize("text"); } /** * Covers one branch */ @Test public void throwsOnCreateResource() { IOException chuck = new IOException("oom?"); Subject subject = new Subject(chuck); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(chuck))); } } /** * Covers three branches */ @Test public void throwsOnWrite() { Subject subject = new Subject(new DummyOutputStream(onWrite, null)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onWrite))); } } /** * Covers one branch - Not needed for coverage if you have the other tests */ @Ignore @Test public void throwsOnClose() { Subject subject = new Subject(new DummyOutputStream(null, onClose)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onClose))); } } /** * Covers two branches */ @SuppressWarnings("unchecked") @Test public void throwsOnWriteAndClose() { Subject subject = new Subject(new DummyOutputStream(onWrite, onClose)); try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(onWrite))); assertThat(e.getCause().getSuppressed(), is(arrayContaining(sameInstance(onClose)))); } } /** * Covers three branches */ @Test public void throwsInTryBlockButCloseableIsNull() throws Exception { IOException chucked = new IOException("ta-da"); Subject subject = new Subject((OutputStream) null) { @Override protected void process(OutputStream closeable) throws IOException { throw chucked; } }; try { subject.scrutinize("text"); fail(); } catch(UncheckedIOException e) { assertThat(e.getCause(), is(sameInstance(chucked))); } } } 

Eclipse覆盖率

警告

虽然不在OP的示例代码中,但有一个不能被testing的情况AFAIK。

如果您将资源引用作为parameter passing,那么在Java 7/8中,您必须有一个局部variables来分配给:

  void someMethod(AutoCloseable arg) { try(AutoCloseable pfft = arg) { //... } } 

在这种情况下,生成的代码仍然会守护资源引用。 在Java 9中 ,在不再需要本地variables的情况下,Synthetic Sugar被更新 : try(arg){ /*...*/ }

补充 – build议使用库来完全避免分支

不得不承认,这些分支中的一部分可能被写成不现实的 – 也就是说,try块在没有空检查的情况下使用AutoCloseable ,或者资源引用( with )不能为null。

通常,应用程序并不在乎它失败的地方 – 打开文件,写入文件或closures文件 – 失败的粒度是不相关的(除非应用程序特别关心文件,例如文件浏览器或文字处理器)。

此外,在OP的代码中,为了testingnullclosurespath – 你必须将try块重构成一个受保护的方法,子类并提供一个NOOP实现 – 所有这些只会覆盖那些绝不会在野外被占用的分支。

我写了一个小型的Java 8库io.earcam.unexceptional (在Maven中心 )处理大多数检查exception样板。

与这个问题相关:它为AutoCloseable s提供了一大堆零分支单行程序,将检查的exception转换为未选中状态。

示例:免费端口查找器

 int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);