7

在查看我的代码覆盖率时,我注意到很多单元测试未能检查 finally 块,这些块试图关闭 finally 块中打开的 InputStreams。

一个示例摘录是:

  try {
      f = new BufferedInputStream(new FileInputStream(source));
      f.read(buffer);
  } finally {
      if (f != null)
          try {
              f.close();
          } catch (IOException ignored) {
          }
      }
  }

是否有任何适当的解决方案可以使用 JUnit4 检查 finally 块内的所有内容?

我知道在保持最大生产力的同时,无法实现 100% 的代码覆盖率。然而,这些红线在报告中有点引人注目。

4

4 回答 4

6

首先考虑使用IOUtils.closeQuietly(),这会将您未经测试的代码(可能是重复的)减少为:

  try {
      f = new BufferedInputStream(new FileInputStream(source));
      f.read(buffer);
  } finally {
      IOUtils.closeQuietly(f);
  }

现在变得很难了。“正确”的方法是将创建外部化BufferedInputStream到另一个类中并注入模拟。拥有一个模拟,您可以验证是否close()调用了适当的方法。

@JeffFoster的答案非常接近我的意思,但是我建议组合而不是继承(以牺牲更多代码为代价):

  try {
      f = fileSystem.open(source);
      f.read(buffer);
  } finally {
      IOUtils.closeQuietly(f);
  }

在生产代码或模拟测试中注入简单真实实现fileSystem的接口实例在哪里。FileSystem

interface FileSystem {

    InputStream open(String file);

}

外部化文件打开的另一个好处是,如果您决定删除缓冲或添加加密,则只需修改一个地方。

有了这个接口,你就可以用 mock 实例化你的测试代码(使用 Mockito):

//given
FileSystem fileSystemMock = mock(FileSystem.class);
InputStream streamMock = mock(InputStream.class);

given(fileSystemMock.open("file.txt")).willReturn(streamMock);

//when
//your code

//then
verify(streamMock).close();
于 2012-01-10T14:37:39.977 回答
5

你可以稍微重构一下代码

public class TestMe {
  public void doSomething() {
    try {
      f = new BufferedInputStream(new FileInputStream(source));
      f.read(buffer);
    } finally {
      if (f != null)
      try {
          f.close();
      } catch (IOException ignored) { }
    }
  }
}

对于这样的事情

public class TestMe {
  public void doSomething() {
    try {
      f = createStream()
      f.read(buffer);
    } finally {
      if (f != null)
      try {
          f.close();
      } catch (IOException ignored) { }
    }
  }

  public InputStream createStream() {
      return new BufferedInputStream(new FileInputStream(source));
  }
}

现在您可以编写测试来捕获输入流类并验证它是否已关闭。(代码很粗糙,但希望你能得到大致的想法)。

public void TestSomething () {
   InputStream foo = mock(InputStream.class); // mock object
   TestMe testMe = new TestMe() {
     @Override
     public InputStream createStream() {
          return foo;
     } 
   }

   testMe.something();

   verify(foo.close());
}

这是否值得,是一个不同的问题!

于 2012-01-10T14:36:51.367 回答
0

您应该注入一个BufferedInputStream模拟 - 或使用工厂创建它 - 并且当close()调用模拟的方法时抛出一个IOException.

此外,除非您在那里没有任何逻辑,否则我不会使用 finally 块。

于 2012-01-10T14:38:41.100 回答
0

我认为您需要问问自己这是否真的值得进行测试。一些测试迷往往会错过尝试实现约 100% 测试覆盖率的收益递减。在这种情况下,看起来一些提议的解决方案增加实际代码的复杂性,以使其“可测试”。我对复杂的测试代码很好,但是为了让它“可测试”而增加实际代码的复杂性让我觉得这是一个糟糕的主意。

于 2012-01-10T14:45:45.857 回答