【问题标题】:Unit testing finally blocks in Java 6Java 6 中的单元测试最终阻塞
【发布时间】:2012-01-10 14:30:36
【问题描述】:

在查看我的代码覆盖率时,我注意到很多单元测试未能检查 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% 的代码覆盖率。然而,这些红线在报告中有点引人注目。

【问题讨论】:

    标签: java exception-handling junit4 java-6 try-catch-finally


    【解决方案1】:

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

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

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

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

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

    其中fileSystemFileSystem 接口的一个实例,在生产代码或模拟测试中注入了简单的实际实现。

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

    【讨论】:

    • 我同意。我确实发现仅在测试中覆盖方法的选项非常有用,但它通常是选择组合的中间步骤。 C# 在这方面是一个 PITA,因为默认情况下方法不是虚拟的,所以我发现我经常不得不跳过整个过程(这很烦人,因为你想尽可能地进行最小的更改)。
    • 谢谢,这正是我想要的 :) 还要感谢 Jeff
    【解决方案2】:

    你可以稍微重构一下代码

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

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

    【讨论】:

      【解决方案3】:

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

      另外,除非你没有任何逻辑,否则我不会使用 finally 块。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-10-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多