如何对此inputStream已关闭进行单元测试?

14

我有一个类似于以下代码的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?

6个回答

18

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

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 已经有一个名为 "closed" 的字段。 - android developer

8
由于没有理由将InputStream暴露在此方法的范围之外,因此您存在测试问题。
但是我假设您并不直接关心InputStream被关闭。 您想要测试它,因为您被告知这是一种好的实践(确实如此)。 但是我认为您真正关心的是流被保留打开的负面影响。 它的影响是什么?
尝试修改此方法,使其不关闭流,然后多次执行它。 您是否会出现内存泄漏,或者用光文件句柄或其他愚蠢的事情? 如果是,则您有一个合理的测试。
或者,只需继续使用装饰过的InputStream,以便可以告诉您它是否已关闭。 将其设置为包保护。 这是“不纯”的但实用的方法。

4
+1,他抓住了重点,测试失败的影响而不是类的内部行为非常重要。 - Tarion

8

要检查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()让其改变行为,就像您使用普通mock一样。


1
Kotlin实现用于测试URL流是否关闭。
//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) }

0
你可以这样做...
    try
    {
         inputStream.readLine();        
    }
    catch (IOException e)
    {
        Assert.assertEquals(e.getLocalizedMessage(), "Stream closed");
    }

0

你可以在测试中写入类似以下内容:

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

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


1
  1. 如果删除close调用,该测试不会失败。
  2. 抛出的IOException无论如何都会导致测试失败。
- Synesso
Synesso,感谢您的评论。
  1. 我们正在讨论使用close()的情况。
  2. 请问您能否解释一下?
- Dedyshka
  1. 是的,但如果生产代码出现问题时测试没有“失败”,那么测试就不好了。这会导致“假阳性”。
  2. 如果您没有捕获IOException,而是让它被抛出,那么它会充分地使测试失败-无需使用try、catch和assert。
- Synesso
你的意思是IOException可能会在流关闭之外的其他地方被抛出吗? - Dedyshka
这不是我的意思。如果run()抛出了IOException,即使没有try/catch块,测试也会失败。因此,不需要try/catch。 - Synesso

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