我有一些代码使用try资源和jacoco它只是一半覆盖。所有的源代码行都是绿色的,但是我得到一个黄色的小符号,告诉我8个分支中只有4个被覆盖。
我无法弄清楚所有分支是什么,以及如何编写涵盖它们的代码。三个可能的地方抛出PipelineException
。这些是createStageList()
,processItem()
和隐含的close()
createStageList()
processItem()
close()
processItem()
和close()
我想不出任何其他情况,但我仍然只有8个中的4个。
有人可以向我解释为什么它是8中的4,并且无论如何都要击中所有8个分支?我不熟悉decyrpting /阅读/解释字节码,但也许你是...... :)我已经看过https://github.com/jacoco/jacoco/issues/82,但是它和它引用的问题都没有帮助(除了注意到这是由于编译器生成的块)
嗯,就在我写完这篇文章的时候,我想到了上面提到的那些案例可能没有被测试过......如果我做对了,我会发一个答案。我相信这个问题,它的答案在任何情况下都会帮助别人。编辑:不,我没找到。抛出RuntimeExceptions(不由catch块处理)没有涵盖任何更多的分支
答案 0 :(得分:56)
嗯,我不能告诉你Jacoco的确切问题是什么,但我可以告诉你如何编译Try With Resources。基本上,有很多编译器生成的开关来处理在各个点抛出的异常。
如果我们采用以下代码并编译它
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控制流。
正如您所看到的,有很多情况可以处理抑制异常的各种可能性。能够涵盖所有这些案件是不合理的。实际上,第一个try块上的goto L59
分支是不可能达到的,因为第一个捕获Throwable将捕获所有异常。
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)
}
答案 1 :(得分:8)
我可以覆盖所有8个分支,所以我的答案是肯定的。看看下面的代码,这只是一个快速的尝试,但它可以工作(或者看看我的github:https://github.com/bachoreczm/basicjava和'trywithresources'包,你可以找到,try-with-resources如何工作,请参阅'ExplaipOfTryWithResources '班级):
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;
}
}
答案 2 :(得分:6)
没有真正的问题,但想在那里投入更多的研究。 tl; dr =看起来你可以实现100%覆盖try-finally,但不能用于try-with-resource。
可以理解的是,老派的try-finally和Java7 try-with-resources之间存在差异。这是两个使用替代方法显示相同内容的等效示例。
旧派示例(尝试终极方法):
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个测试获得Old School示例的100%行,指令和分支覆盖:
分析:java-7示例:
如果针对Java7样式示例运行相同的4个测试,则jacoco表示覆盖6/8个分支(在try本身上)和在try中的null-check上2/2。我尝试了一些额外的测试以增加覆盖率,但我发现没有办法比6/8更好。正如其他人所指出的那样,java-7示例的反编译代码(我也看到了)表明java编译器正在为try-with-resource生成无法访问的段。 Jacoco正在(准确地)报告此类细分市场存在。
更新:使用Java7编码样式,您可以使用Java7 JRE获得100%覆盖 IF (请参阅下面的Matyas响应)。但是,使用带有Java8 JRE的Java7编码风格,我相信你会覆盖6/8分支机构。相同的代码,只是不同的JRE。看起来两个JRE之间的字节代码创建方式不同,Java8创建了无法访问的路径。
答案 3 :(得分:5)
四岁,但仍然......
AutoCloseable
AutoCloseable
try
区块中投掷,但AutoCloseable
为空上面列出了所有7个条件 - 8个分支的原因是由于重复的条件。
可以到达所有分支,try-with-resources
是相当简单的编译器糖(至少与switch-on-string
相比) - 如果无法访问它们,那么根据定义它就是编译器错误。
实际上只需要6个单元测试(在下面的示例代码中,throwsOnClose
是@Ingore
d,分支覆盖范围是8/8。
另请注意Throwable.addSuppressed(Throwable)无法抑制自身,因此生成的字节码包含一个额外的保护(IF_ACMPEQ - 引用相等)来防止这种情况。幸运的是,这个分支由throw-on-write,throw-on-close和throw-on-write-and-close情况所覆盖,因为字节码变量槽由3个异常处理程序区域中的外部2个重用。
Jacoco的问题不 - 实际上链接的issue #82中的示例代码不正确,因为没有重复的空检查,并且关闭周围没有嵌套的catch块
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)));
}
}
}
虽然不在OP的示例代码中,但有一个案例无法通过AFAIK进行测试。
如果您将资源引用作为参数传递,那么在Java 7/8中,您必须有一个要分配的局部变量:
void someMethod(AutoCloseable arg)
{
try(AutoCloseable pfft = arg) {
//...
}
}
在这种情况下,生成的代码仍将保护资源引用。合成糖为updated in Java 9,其中不再需要局部变量:try(arg){ /*...*/ }
不可否认,其中一些分支可能被写为不切实际 - 即try块使用AutoCloseable
而不进行空检查或资源引用(with
)不能为空。
通常您的应用程序并不关心它失败的地方 - 打开文件,写入文件或关闭文件 - 失败的粒度无关紧要(除非应用程序特别关注文件,例如文件浏览器或文字处理器。)
此外,在OP的代码中,要测试null可关闭路径 - 您必须将try块重构为受保护的方法,子类并提供NOOP实现 - 所有这些只是得到覆盖永远不会被野外采取的树枝。
我写了一个小的Java 8库io.earcam.unexceptional(在Maven Central中),它处理大多数已检查的异常样板。
与此问题相关:它为AutoCloseable
提供了大量零分支单行,将已检查的异常转换为未选中。
示例:免费端口查找器
int port = Closing.closeAfterApplying(ServerSocket::new, 0, ServerSocket::getLocalPort);
答案 4 :(得分:2)
Jacoco最近解决了这个问题,版本0.8.0(2018/01/02)
&#34;在创建报告期间,会过滤掉各种编译器生成的工件,否则需要不必要的,有时甚至是不可能的技巧,以避免部分或错过覆盖:
答案 5 :(得分:1)
我遇到类似的问题:
try {
...
} finally {
if (a && b) {
...
}
}
它抱怨说8个分支中有2个没有被覆盖。最后这样做了:
try {
...
} finally {
ab(a,b);
}
void ab(a, b) {
if (a && b) {
...
}
}
没有其他变化,我现在达到了100%......