【问题标题】:How can I unit test this inputStream has been closed?如何单元测试此 inputStream 已关闭?
【发布时间】:2012-11-27 19:49:40
【问题描述】:

我有一个Runnable,类似于:

    public void run() {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            //more stuff here
        } 
        catch (Exception e) {
            //simplified for reading
        }
        finally {
            if(inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {}
            }
        }
    }

如何测试inputStream.close() 是否被调用?我目前正在使用 Mockito 和 JUnit。我知道注入inputStream 是一个想法,但我不希望在调用run?() 之前使用这些资源,因此它是一个局部变量。那么如何重新设计我的代码以允许我测试是否调用了 close 呢?

【问题讨论】:

    标签: java unit-testing junit mockito


    【解决方案1】:

    如果我正确理解了任务,它可能是这样的

    static boolean isClosed;
    
    public void run() {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file) {
                @Override
                public void close() throws IOException {
                    isClosed = true;
                    super.close();
                }
            };
            // more stuff here
    

    【讨论】:

    • 顺便说一句,关于 FileInputStream 的有趣之处在于它已经有一个“关闭”字段。
    【解决方案2】:

    由于没有理由将 InputStream 暴露在此方法的范围之外,因此您遇到了测试问题。

    但我假设您并不直接关心 InputStream 被关闭。你想测试它,因为你被告知这是一种很好的做法(而且确实如此)。但我认为你真正关心的是保持开放的流的负面影响。效果如何?

    尝试修改此方法,使其不关闭流,然后多次执行。您是否遇到内存泄漏,或者文件句柄用完或其他一些愚蠢的行为?如果是这样,您就有了合理的测试。

    或者,直接公开一个装饰的 InputStream,它可以告诉您它是否已关闭。使其包保护。这就是“不纯”但务实的做法。

    【讨论】:

    • +1,他明白了,重要的是测试失败的影响而不是类的内部行为。
    【解决方案3】:

    要检查是否调用了 close() 方法,您可以使用 Mockito.spy() 创建一个可以记住调用的代理对象。 Spy 将所有调用委托给底层 InputStream,只记住发生了什么:

    InputStream inputStreamSpy = Mockito.spy(inputStream);
    // a code that is expected to close your stream goes here ...
    Mockito.verify(inputStreamSpy).close();
    

    实际上,这并不能解决您注入 InputStream 实例的问题。似乎您需要某种可以为您打开流的工厂,并且您可以在单元测试中模拟该工厂。我们称这个工厂为 FileSystem:

    public class FileSystem {
        public FileInputStream newFileInputStream(File file) {
            return new FileInputStream(file);
        }
    }
    

    现在,您可以注入 FileSystem 的一个实例,它不会在 run 方法执行之前使用资源:

    public void run() {
        InputStream inputStream = null;
        try {
            inputStream = fileSystem.newFileInputStream(file);
            //more stuff here
        } 
        catch (Exception e) {
            //simplified for reading
        }
        finally {
            if(inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {}
            }
        }
    }
    
    @Test
    public void runShouldCloseInputStream() {
        InputStream inputStream = ...
        InputStream inputStreamSpy = Mockito.spy(inputStream);
        FileSystem fileSystemMock = Mockito.mock(FileSystem.class);
        when(mockFileSystem.newFileInputStream(Mockito.any(File.class)))
            .thenReturn(inputStreamSpy);
    
        MyRunnable instance = new MyRunnable(mockFileSystem);
        instance.run();
    
        verify(inputStreamSpy).close();
    }
    

    Spy 可以做的不仅仅是监听,您可以使用 Mockito.when() 教它改变行为,就像使用常规模拟一样。

    【讨论】:

      【解决方案4】:

      用于测试 URL 流的 Kotlin 实现已关闭

      //close the connection
      streamURL.close()
      
      //stream should not be available if it is closed
      try { streamURL.available() }
      
      //java.net.URL provides simple "closed" message on IO URL
      catch (ex: IOException) { Assert.assertEquals("closed", ex.message) }
      

      【讨论】:

        【解决方案5】:

        你可以在测试中这样写:

        try {
            run();
        } catch (IOException e) {
            Assert.fail();
        }
        

        当你的方法关闭 strem 并发生异常时,测试将失败。

        【讨论】:

        • 1.如果删除 close 调用,该测试不会失败。 2. 抛出的IOException 无论如何都会通过测试。
        • Synesso,感谢您的评论 1. 我们用 close() 讨论案例; 2. 你能解释一下吗?
        • 1.是的,但是如果在生产代码被破坏时它没有失败,那么测试就不好了。你得到一个“误报”。 2. 如果你没有抓住IOException,而是让它被抛出,它就完全没有通过测试——不需要trycatchassert
        • 您的意思是 IOException 可以在流关闭时以外的其他地方抛出?
        • 我不是这个意思。如果run() 抛出IOException,即使没有try/catch 块,测试也会失败。所以不需要 try/catch。
        【解决方案6】:

        你可以这样做......

            try
            {
                 inputStream.readLine();        
            }
            catch (IOException e)
            {
                Assert.assertEquals(e.getLocalizedMessage(), "Stream closed");
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-08
          • 2011-11-07
          • 2010-12-21
          • 2017-12-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多