Mockito 和 BufferedReader 的最佳实践

3

我正在尝试测试一个处理文件/流的类。例如,方法readFile将使用BufferedReader返回一个包含文件内每行字符串的ArrayList

public ArrayList<String> readFile(String fileName)
{
    ArrayList<String> result = new ArrayList<String>();
    FileReader fr = null;
    BufferedReader br = null;
    try {

        fr = new FileReader(STORAGE_DIRECTORY+fileName);
        br = new BufferedReader(fr);
        String sCurrentLine;

        while ((sCurrentLine = br.readLine()) != null) {
            result.add(sCurrentLine);
        }
    }
        catch (FileNotFoundException e) {


            return new ArrayList<String>();
        } catch (IOException e) {

            return new ArrayList<String>();
        }

        br.close();
        fr.close();


        return result;
    }

但是当我使用Mockito来模拟bufferedReader方法“readLine()”时,会因为FileReader构造函数抛出FileNotFoundException。我必须使用temporaryFile或者mock FileReader构造函数吗?

@Test
public void readFileTest5() throws Exception {
    BufferedReader bufferedReader = Mockito.mock(BufferedReader.class);
    FileReader fileReader = Mockito.mock(FileReader.class);
    when(BufferedReader.readLine()).thenReturn("abc");
    assertEquals("",new ArrayList<String>(), FileUtil.readFile("abc"));
}

谢谢您


我的错,我应该写在类的顶部。BufferedReader是一个使用注解(Spring风格)注入的类的实例。@MockBean private BufferedReader bufferedReader;@MockBean private java.io.FileReader fileReader;@MockBean private File File;@InjectMocks private com.example.system.FileUtil FileUtil; - Nicogo
1
你应该将缓冲区构造提取到单独的方法中,并模拟该方法返回模拟的BufferedReader,其“readLine”返回您想要的内容。或者可能不要尝试模拟这个简单的东西:您有更明显的资源泄漏问题。 - M. Prokhorov
好的,谢谢你。你说的“资源泄漏”是什么意思? - Nicogo
发生异常时,您没有关闭读取器。 - M. Prokhorov
谢谢,我刚刚删除了一些代码以便专注于我的问题,但我已经解决了。 - Nicogo
2个回答

4
readFile中,BufferedReader 封装了FileReader,而FileReader是在该方法内部创建的,因此您无法模拟FileReader,这意味着您无法模拟输入到BufferedReader实例中的内容。
这种方法使得测试变得困难。
我建议您改变您的方法。例如:
public ArrayList<String> readFile(BufferedReader reader) {
    // ...
}

那么您的测试可能是这样的:
@Test
public void readFileTest() throws Exception {
    BufferedReader bufferedReader = Mockito.mock(BufferedReader.class);
    Mockito.when(bufferedReader.readLine()).thenReturn("a", "b", "c", null);
    List<String> expected = Arrays.asList("a", "b", "c");
    Assert.assertEquals("", expected, readFile(bufferedReader));
}

甚至可以完全不使用Mockito:

@Test
public void readFileTest() throws Exception {
    BufferedReader bufferedReader = new BufferedReader(new StringReader("a\nb\nc"));
    List<String> expected = Arrays.asList("a", "b", "c");
    Assert.assertEquals("", expected, readFile(bufferedReader));
}

另一种方法是创建一个实际的文件并读取它,无需模拟任何内容。您可以使用JUnit的Temporary Folder规则来协助测试后清理。

另外注意:`readFile()`方法没有安全地关闭`BufferedReader`和`FileReader`。


谢谢,我今天下午看了你的答案。 我想在我的情况下,它只是将问题移动到另一个步骤(可能存在结构问题)。 但我现在有另一个类中使用readFile()的方法,调用如下: lines = fileUtil.readFile(new BufferedReader(new FileReader(new File(STORAGE_DIRECTORY,FilenameUtils.getName(context))))); 在这个方法的调用周围,我对返回的List进行了一些处理。 但是,当我想测试此方法时,即使我模拟该方法,构造函数中的文件也找不到。最好的方法是什么? 创建临时文件吗? - Nicogo
1
我不确定我是否正确理解了你的最后一篇帖子,但肯定的是,避免在这里使用Mockito的最简单方法是(a)将预填充的BufferedReader传递到fileUtil.readFile()中(就像我在我的答案中展示的第二个示例测试中所示),或者(b)在测试用例中创建一个真实的文件,使用它,然后丢弃它(我的JUnit临时文件夹规则链接向您展示了一种优雅的方式来处理创建-然后丢弃语义)。 - glytching

1

由于您的FileUtil确实读取了一个按名称引用的文件,因此您需要在文件系统中创建一个文件。这可以通过以下简单方法完成:

File tempFile = File.createTempFile("temp", ".tmp");
tempFile.deleteOnExit(true);

一些关于你的实现代码的话:
ArrayList<String> result = new ArrayList<String>();
// combine directory and file name like this
File f = new File(STORAGE_DIRECTORY, fileName);
// use try-with-resource here, like this:
try (BufferedReader br = new BufferedReader(new FileReader(f)))  {

    String sCurrentLine;

    while ((sCurrentLine = br.readLine()) != null) {
        result.add(sCurrentLine);
    }
    } catch (FileNotFoundException e) {
        // no need to create a new list
        return Collections.emptyList()
    } catch (IOException e) {
        return Collections.emptyList()
    }

    // you don't need to close the reader if you use try-with-resource

    return result;
}

当然,您可以直接使用Files#readAllLines。
try {
    return Files.readAllLines(Paths.get(STORAGE_DIRECTORY, fileName), StandardCharsets.UTF-8);
} catch (IOException e) {
    return Collections.emptyList();
}

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