Java 6中如何对finally块进行单元测试

7

在检查我的代码覆盖率时,我发现许多单元测试未能检查finally块中尝试在其中关闭打开的InputStream的情况。

一个示例摘录如下:

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

有没有合适的解决方案来使用JUnit4检查finally块内的所有内容?

我知道在考虑最大生产率的情况下无法实现100%的代码覆盖率。然而,这些红线在报告中具有一定的吸引力。

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);

}

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

有了这个接口,您可以使用模拟对象(使用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();

我同意。我确实发现在测试中仅覆盖方法的选项非常有用,但通常这是选择组合的中间步骤。在这方面,C#很让人头疼,因为方法默认情况下不是虚拟的,所以我经常不得不一口气跳到最后(这很烦人,因为你想尽可能地进行最小的更改)。 - Jeff Foster
谢谢,这正是我在寻找的!也感谢Jeff。 - fyr

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());
}

这是否值得,是另一个问题!

0
你应该注入一个模拟的BufferedInputStream - 或者使用工厂创建它 - 当模拟的close()方法被调用时,抛出一个IOException
此外,在你没有任何逻辑之前,我不会让上面的finally块执行。

0

我认为你需要问问自己,这是否真的值得测试的努力。有些测试迷往往忽视了试图实现近乎100%的测试覆盖率所带来的收益递减。在这种情况下,看起来一些提出的解决方案增加了更多的复杂性到实际代码中,以使其“可测试”。我对复杂的测试代码感到满意,但是为了使其“可测试”而向实际代码添加复杂性,我认为这是一个可怕的想法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接