【问题标题】:Unit test that an exception has been caught and handled单元测试异常已被捕获和处理
【发布时间】:2015-01-27 15:28:57
【问题描述】:

我写了一个使用BufferedReader和FileReader的方法,结果就是需要处理FileNotFoundException和IOException。

现在我在我的类中使用了 try 和 catch 语句来处理异常,所以那里没有问题。

我遇到的问题是编写一个测试来验证异常是否已被捕获。

我无法编写如下测试,因为我的方法已经捕获了异常,因此它不会传播到测试中进行处理:

@Test(expected = FileNotFoundException.class)
public void testFileNotFound() {
    ...
};

如何测试是否已捕获异常?我是否需要创建我的类的模拟对象并验证是否使用 Mockitio 之类的东西捕获了异常?还是不创建一个 mock 并使用 Mockitio 来监视实际对象以验证是否捕获了异常?

编辑:这是我要测试的方法...

public List<String[]> parse(String fileToRead) {

    BufferedReader fileReader = null;
    List<String[]> parsedData = new ArrayList<String[]>();
    String currentLine;

    try {
        fileReader = new BufferedReader(new FileReader(fileToRead));

        while ((currentLine = fileReader.readLine()) != null) {
            String[] parsedLine = currentLine.split(",");
            parsedData.add(parsedLine);
        }
    }
    catch (FileNotFoundException e) {
        logger.error("File does not exist", e);
    }
    return parsedData;
}

【问题讨论】:

  • expected 表示您希望它被抛出。如果它被捕获,则在它被捕获后通过验证方法逻辑的其余部分进行测试。
  • 向我们展示一个最小但相关的测试方法示例。
  • 添加了我希望测试的方法示例
  • 我认为你看错了。您无需验证异常是否被捕获。编译器和/或您的 IDE 能够解决这个问题。你需要什么——以及一个好的单元测试的重点是什么——是验证你的代码在抛出异常时是否按预期工作。例如,如果您需要区分“不存在的文件”和“空文件”,则需要针对这些情况编写测试。
  • 第二个 dcsohl,您的测试没有理由检查如何您的组件是如何实现的——您只需要确保指定的输入(不存在的文件)给出正确的输出(即正常返回,没有未捕获的异常)。如果 JUnit 测试遇到未捕获的异常,它们就会失败,因此检查静默异常捕获可能只是在测试中包含一个或两个不存在的文件。但是,如果有任何可公开观察的状态或行为伴随着不存在的文件,那么您一定要明确检查。

标签: java unit-testing exception mocking mockito


【解决方案1】:

我认为这是尝试与语言对抗而不是使用它的经典示例。它也可能是设计上不太可测试的代码示例。

首先,了解受检异常和未受检异常之间的概念区别很重要。像NullPointerExceptionArrayIndexOutOfBoundsException 这样的未经检查的异常是“未经检查的”(意味着编译器不关心它们),因为当它们被抛出时,通常是程序员的错。这些类型的异常通常可以在方法调用之前使用 if 语句轻松防范。另一方面,IOExceptionFileNotFoundException 被“检查”(意味着编译器会抱怨缺少 catch/throw),因为即使您在尝试读取文件之前使用 File#exists() 方法检查文件是否存在,同时可能会发生外部事件导致文件丢失,或者在您读取文件时可能会从机器中取出硬盘驱动器。检查的异常通常表示一种崩溃状态,您必须具体决定如何从中恢复,并且只有您(作为程序员)才能做出该决定。

考虑你的方法。您如何通过方法的输出判断读取操作是失败还是使用空文件成功?你不能,因为即使这两个事件完全不同,你处理它们的方式完全相同。这可能是您想要的,也可能不是您想要的,但与您调用该方法的位置不同,处理这两种情况可能更合适。例如,您可能希望向用户显示“文件为空”或“文件不存在”的消息,而不是“无数据”。

其次,可测试性问题(这可能是也可能不是问题,具体取决于您的设置)。 catch-clause 中唯一发生的事情(因此是引发和捕获异常的唯一迹象)是臭名昭著的“记录并忘记”策略。您正在记录错误(而不是在适当的情况下对其进行处理/反应),因此要验证是否捕获了异常,您唯一真正的选择是模拟记录器(如果您的设计允许)并验证其 error() 方法是叫。如果您的目标是验证在给定特定输入的情况下是否引发异常,您不能只检查输出,因为这只是验证输出 - 一个完全不同的测试。您必须检查是否采用了引发异常的代码路径,在这种情况下,一种方法是模拟记录器。

【讨论】:

  • 感谢您的详细回复。我会进一步解释我的情况。我发布的方法是我正在使用的方法的简化版本,因此 catch 语句只记录异常。我可以向您保证,我实际上正在处理该错误。该方法也被简化为只读取一个文件。在我的实际实现中,我将文件列表传递给要读取的方法。继续...
  • Continued... 你认为通过包含一些存在和不存在的文件列表然后检查输出的测试是正确的吗? FileNotFoundException 将被处理,以便程序不会停止执行,而是记录并通知文件不存在但继续读取确实存在的文件,因此作为输出,我希望确实存在的文件是阅读和那些不被记录的。
  • 一种测试方法 " 给定一个文件的输入,其中一些存在而一些不存在,输出包含现有文件内容的字符串列表,并调用 error() 方法在不存在文件的记录器上" 将只提供一个文件列表和一个模拟记录器,然后验证模拟的 error() 方法是否被调用了预期的次数(使用预期的参数),并且输出符合预期。您也许可以用 InputStream 替换 File(或 String),这可能会使存根更容易(您可以将实际测试中的“文件”内容指定为字符串)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-25
  • 2011-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-06
  • 1970-01-01
相关资源
最近更新 更多