【问题标题】:Try catch in a JUnit test在 JUnit 测试中尝试 catch
【发布时间】:2015-10-04 02:16:27
【问题描述】:

我正在为一个已经存在很长时间的应用程序编写单元测试。我需要测试的一些方法是这样构建的:

public void someMethod() throws Exception { 
   //do something 
}

如果我想测试这些方法,我必须在我的单元测试中写这样的东西:

@Test
public void someTest() {
   try {
      someMethod();
   }
   catch (Exception e) {
      e.printStackTrace();
   }
}

这样做是个好习惯吗?还是有其他方法可以测试这些方法?

我在互联网上进行了一些研究,发现了一些带有@Rule 注释和@Test(expected=Exception.class) 的解决方案,但这不起作用(Eclipse 一直在测试中将someMethod() 行显示为错误)。 我不知道这些是否是好的解决方案,因为我对整个单元测试故事还很陌生。

如果有对此了解很多的人可以帮助我,我将非常感激。

【问题讨论】:

  • 不要抑制异常,除非您希望测试通过无论是否抛出异常。请注意,通过“是否”通过诸如异常发生之类的测试可能没有那么有用。
  • 您可以通过将 JUnit 添加到您的方法 sig 中来让 JUnit 处理异常:public void someTest() throws Exception。但是,如果您想自己捕获异常以断言它被捕获,那么您给出的示例很好。
  • @Makoto JUnit 通过打印堆栈跟踪和测试失败来“处理它”。
  • @immibis:是的,这是真的。不过按照惯例,声明要抛出的异常是让一些更高级别的框架或 JVM 来处理它。在这一点上我应该更清楚。
  • 这是否是一个好主意取决于您要做什么。

标签: java unit-testing junit try-catch


【解决方案1】:

由于Exception 是一个检查异常,您可以:

  • 必须在try...catch 语句中捕获异常,或者
  • 声明要在方法本身中抛出的异常。

你在那里工作正常,但我个人的偏好是声明要抛出的异常。这样,如果在测试运行期间抛出了我不期望的异常,测试将失败。

@Test
public void someTest() throws Exception {
    // dodgy code here
}

如果我们需要查看是否抛出了特定的异常,那么您可以选择使用@Rule 或直接将值添加到@Test 注释中。

@Test(expected = FileNotFoundException.class)
public void someTest() throws Exception {
    // dodgy code here
}

在 JUnit 5 中,您可以利用 Assertions.assertThrows 来完成同样的事情。我对这个整体不太熟悉,因为在编辑时它还不是 GA,但它似乎接受来自 JUnit 5 的 Executable。

@Test
public void someTest() {
    assertThrows(FileNotFoundException.class, () ->
         { dodgyService.breakableMethod() };
}

【讨论】:

  • 事实是:我不希望抛出异常。如果不抛出异常,我的测试不会失败吗?
  • 不!如果没有抛出异常,那么只要满足您的其他断言,测试就会正常通过。
  • 哦,好吧,我想我不太明白。谢谢!
  • 请记住,仅仅因为您不希望在生产系统中抛出异常,并不意味着确保在测试中抛出异常不是一个好主意,并且处理是明智的-在日志中添加注释,告知用户失败等,干净地关闭系统,关闭线程〜您永远不知道网络中断何时会导致找不到文件或打印机不可用等等。不要让您的用户 PC 由于等待线程释放或资源再次可用的处理不当的异常而死机。
  • “你那里的东西工作正常”?没办法,太可怕了。
【解决方案2】:
@Test
public void someTest() {
   try {
     someMethod();
   }
   catch (Exception e) {
     Assert.fail("Exception " + e);
   }
}

如果不应该发生异常,您可以做什么。另一种方法是在签名中抛出异常,如下所示:

@Test
public void someTest() throws Exception {
     someMethod();
}

不同之处在于,在一种情况下,测试会因断言异常而失败,而在另一种情况下,测试会因为测试崩溃而失败。 (就像在你的代码中的某个地方你会得到一个 NPE 并且测试会因此而发生)

之所以必须这样做,是因为 Exception 是一个已检查的异常。见Checked versus unchecked exception

@Test(expected=Exception.class) 用于测试,用于测试是否会抛出异常。

@Test(expected=ArrayIndexOutOfBounds.class)
public void testIndex() {
   int[] array = new int[0];
   int var = array[0]; //exception will be thrown here, but test will be green, because we expect this exception

}

【讨论】:

  • 这重新实现了 junit 处理异常的方式,但更糟糕的是,由于您丢失了堆栈跟踪,因此很难知道异常发生在哪里。
【解决方案3】:

不要在测试代码中捕获应用程序的异常。相反,声明它向上抛出。

因为,当 JUnit 的 TestRunner 发现抛出异常时,它会自动将其记录为 error 用于测试用例。

仅当您 testcase 期望该方法应抛出 Exception 时,您才应使用 @Test(expected=Exception.class) 或捕获异常。

在其他情况下,只需将其向上扔,

public void someTest() throws Exception {

【讨论】:

    【解决方案4】:

    您可以在测试方法签名中添加异常。然后,如果你正在测试是否抛出异常,你必须使用@Test(expected=Exception.class)。在没有抛出异常的测试用例中,测试将成功通过。

    @Test
    public void testCaseWhereExceptionWontBeThrown() throws Exception {
        someMethod(); //Test pass
    }
    
    @Test(expected = Exception.class)
    public void testCaseWhereExceptionWillBeThrown() throws Exception {
        someMethod(); //Test pass
    }
    

    【讨论】:

    • 我认为一些示例代码会帮助您回答问题,因为我认为他可能已经尝试过类似的东西,但语法不正确。
    • 对我来说,第二个代码块失败了,因为我正在测试的代码有一个 try-catch 块。我只是想测试当我使用错误值调用时是否发生特定异常。
    【解决方案5】:

    关于如何在 Junit 测试人员处处理异常有两个主要规则:

    1. 如果异常源自测试代码:

      • 如果需要,请在Test 注释的expected 属性中声明它。或者,如果应该对异常对象本身进行进一步检查,则捕获它并忽略它。 (在这种情况下,还必须在 try 块的末尾调用 Assert.fail,以表明没有产生预期的异常。
      • 如果不是预期的,捕获它并执行 Assert.fail。 (之前拨打Exception.printStackTrace 也很有用)。
    2. 1234563 em>),在throws 子句处重新抛出它。

    为什么您应该期待测试器中出现异常?提醒:您应该为测试代码上的每个可能结果编写一种测试方法(以实现高代码覆盖率):在您的情况下,一种方法必须成功返回,并且至少另一种方法必须产生异常。

    【讨论】:

      【解决方案6】:

      关于JUnit的三点:

      • 测试应该是精确的,它们应该完全根据测试输入的设置方式明确地通过或失败。

      • 测试应该将失败报告回框架。

      • 测试不应依赖于读取其输出。

      您的示例在所有三个方面都失败了。无论是否抛出异常,测试仍然通过。如果抛出异常,JUnit 永远不会发现它并且不能将其包含在测试结果中。知道出了什么问题的唯一方法是读取测试写入标准输出的内容,这使得错误太容易被忽略。这不是编写测试的有用方法。

      JUnit 旨在让做正确的事情变得容易,并为开发人员提供有用的反馈。如果从测试方法中抛出异常,它会被框架捕获。如果测试带有异常注释,表明该异常是预期的,则框架将测试标记为通过。否则,框架无法通过测试并记录堆栈跟踪以进行报告。该框架会报告哪些断言失败以及发生了哪些意外异常,以便每个人都知道测试是否有效。

      如果您希望测试成功而不会引发异常,那么如果测试中的任何内容都可以引发已检查异常,请将throws Exception 添加到测试方法签名中。在签名中添加throws 并不是说​​方法必须抛出任何东西,它只是让任何发生的异常都被抛出,以便测试框架可以捕获它们。

      您在测试中实际捕获异常的唯一实例是您要测试有关异常的断言;例如,您可以测试有关异常的消息是否符合您的预期,或者异常是否设置了原因。在这种情况下,您可以在 try-block 的末尾添加 Assert.fail(),这样如果不抛出异常就会导致测试失败。

      不是 try-catch 块那么糟糕,而是缺少任何会导致测试失败的东西。

      当你一开始写一个测试时,让它失败。这样你就可以向自己证明你知道测试在做什么,并且你确认,当失败时,你会意识到它。

      【讨论】:

        【解决方案7】:

        这是什么异常?是吗

        1. 使用单元测试中不会发生的流之类的异常或
        2. 可能由于某种错误输入而发生的异常?

        如果是 1。我会把它放在方法签名级别,因为 try-catch 除了仪式之外没有任何实际用途。

        @Test
        public void testFoo() throws Exception {
            // ...
        }
        

        如果是 2. 它会变得有点复杂。如果抛出异常,您需要问自己应该发生什么。测试应该失败吗?是预期的吗?这无关紧要吗?以下示例说明了如何处理所有这些。 注意:我只使用了异常,因为你使用了。我希望它真的不是,因为如果有可能抛出一些 other 异常而不是预期的那么这些将非常不稳定。如果可能的话不要使用Exception,使用更具体的东西(在junit 和代码中)。

        // The below code assumes you've imported the org.junit.Assert class.
        
        @Test
        public void thisShouldFailIfExceptionCaught() {
            //Given...
            try {
                // When...
            } catch (Exception e) {
                Assert.fail();
            }
            // Then...
        }
        
        @Test
        public void thisShouldPassOnlyIfTheExceptionIsCaught() {
            //Given...
            try {
                // When...
                Assert.fail();
            } catch (Exception expected) {}
            // No "then" needed, the fact that it didn't fail is enough.
        }
        
        @Test
        public void irrelevantExceptionThatCouldBeThrown() {
            //Given...
            try {
                // When...
            } catch (Exception e) {}
            // Then...
        }
        

        【讨论】:

        • 这取决于我使用的方法。可以是JMSException、IOException 或ConfigurationException :) 感谢您的启发性回答!
        • @Nelsch 没问题!我会建议将来在提问时不要使用Exception,除非你真的是认真的(例如测试throws Exception),即使像SomeCheckedException或SomeUncheckedException这样的名字也能真正解释手头的问题。
        • 单元测试不应该仅仅为了调用fail()而捕获异常。事实上,所有单元测试都声明Exception 完全没问题,因为测试方法(应该!)从不从除测试框架之外的任何地方调用。后者只是让测试失败,以防出现您想要的异常。
        猜你喜欢
        • 2019-03-07
        • 2022-11-10
        • 1970-01-01
        • 2012-06-21
        • 1970-01-01
        • 2014-12-22
        • 1970-01-01
        • 1970-01-01
        • 2012-02-26
        相关资源
        最近更新 更多