如何验证未抛出异常

时间:2013-07-02 12:59:09

标签: java mocking mockito powermock

在使用Mockito的单元测试中,我想验证是否未抛出NullPointerException

public void testNPENotThrown{
    Calling calling= Mock(Calling.class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
}

我的测试设置了testClass,设置了Calling对象和属性,以便该方法将抛出NullPointerException

验证从不调用Calling.method()。

public void testMethod(){
    if(throw) {
        throw new NullPointerException();
    }

    calling.method();
}

我希望测试失败,因为它会抛出NullPointerException,然后我想写一些代码来解决这个问题。

我注意到的是测试总是通过,因为测试方法永远不会抛出异常。

4 个答案:

答案 0 :(得分:15)

<强> TL;博士

  • JDK8之前:我会推荐旧的try - catch块。

  • post-JDK8:使用AssertJ或自定义lambdas来断言异常行为。

长篇故事

可以自己编写自己动手 try - catch阻止或使用JUnit工具(@Test(expected = ...)@Rule ExpectedException JUnit规则功能)。

但是这些方式并不是那么优雅,并且不能将可读性明智地与其他工具混合在一起。

  1. try - catch阻止你必须围绕测试行为编写块,并在catch块中写下断言,这可能没什么问题,但是很多人发现这个样式中断了测试的阅读流程。你还需要在Assert.fail块的末尾写一个try,否则测试可能会错过断言的一面; PMD findbugs Sonar 会发现此类问题。

  2. @Test(expected = ...)功能很有意思,因为您可以编写更少的代码,然后编写此测试可能不太容易出现编码错误。 这种方法缺乏一些领域。

    • 如果测试需要检查异常的其他内容,例如原因或消息(良好的异常消息非常重要,那么具有精确的异常类型可能还不够)。
    • 此外,由于期望在方法中存在,取决于测试代码的编写方式,然后测试代码的错误部分可能抛出异常,导致误报测试,我不确定< em> PMD , findbugs Sonar 将提供有关此类代码的提示。

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. ExpectedException规则也是尝试修复之前的警告,但使用期望样式感觉有点尴尬, EasyMock 用户非常了解这种风格。对某些人来说可能很方便,但是如果你遵循行为驱动开发(BDD)或安排行为断言(AAA)原则,那么ExpectedException规则就会赢得&#39;适合那些写作风格。除此之外,它可能会遇到与@Test方式相同的问题,具体取决于您预期的位置。

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    即使预期的异常放在测试语句之前,如果测试遵循BDD或AAA,它也会破坏您的阅读流程。

    另请参阅ExpectedException作者的JUnit上的comment问题。

  4. 因此,上述选项包含所有注意事项,并且显然不会对编码器错误产生影响。

    1. 在创建了一个看起来很有前途的答案后,我意识到了一个项目catch-exception

      正如项目描述所说的那样,它让编码器用流畅的代码行编写代码来捕获异常并为以后的断言提供此异常。您可以使用任何断言库,如HamcrestAssertJ

      从主页获取的一个快速示例:

      // given: an empty list
      List myList = new ArrayList();
      
      // when: we try to get the first element of the list
      when(myList).get(1);
      
      // then: we expect an IndexOutOfBoundsException
      then(caughtException())
              .isInstanceOf(IndexOutOfBoundsException.class)
              .hasMessage("Index: 1, Size: 0") 
              .hasNoCause();
      

      正如您所看到的,代码非常简单,您可以在特定行上捕获异常,then API是将使用AssertJ API的别名(类似于使用assertThat(ex).hasNoCause()...)。 在某些时候,该项目依赖于FEST-Assert的AssertJ 的祖先。 编辑:该项目似乎正在酝酿Java 8 Lambdas支持。

      目前这个图书馆有两个缺点:

      • 在撰写本文时,值得注意的是,该库基于Mockito 1.x,因为它创建了场景背后测试对象的模拟。由于Mockito仍未更新此库无法与最终类或最终方法一起使用。即使它是基于当前版本的mockito 2,这也需要声明一个全局模拟制作者(inline-mock-maker),这可能不是你想要的东西,因为这个模拟器具有与常规模拟器不同的缺点。

      • 它需要另一个测试依赖。

      一旦库支持lambdas,这些问题就不会适用,但AssertJ工具集会复制该功能。

      如果您不想使用catch-exception工具,请考虑所有问题,我将推荐try - catch块的旧方法,至少直到JDK7。对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是断言异常。

    2. 使用JDK8,lambdas进入测试场景,并且它们被证明是一种断言异常行为的有趣方式。 AssertJ已经更新,提供了一个很好的流畅API来断言异常行为。

      使用AssertJ的示例测试:

      @Test
      public void test_exception_approach_1() {
          ...
          assertThatExceptionOfType(IOException.class)
                  .isThrownBy(() -> someBadIOOperation())
                  .withMessage("boom!"); 
      }
      
      @Test
      public void test_exception_approach_2() {
          ...
          assertThatThrownBy(() -> someBadIOOperation())
                  .isInstanceOf(Exception.class)
                  .hasMessageContaining("boom");
      }
      
      @Test
      public void test_exception_approach_3() {
          ...
          // when
          Throwable thrown = catchThrowable(() -> someBadIOOperation());
      
          // then
          assertThat(thrown).isInstanceOf(Exception.class)
                            .hasMessageContaining("boom");
      }
      
    3. 通过几乎完全重写JUnit 5,断言已经improved一点,它们可能被证明是一种开箱即用的方式来断言正确的异常。但真的断言API仍然有点差,assertThrows以外什么都没有。

      @Test
      @DisplayName("throws EmptyStackException when peeked")
      void throwsExceptionWhenPeeked() {
          Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
      
          Assertions.assertEquals("...", t.getMessage());
      }
      

      正如您所注意到的,assertEquals仍在返回void,因此不允许链接断言,如AssertJ。

      此外,如果您记得与MatcherAssert发生冲突,请准备好与Assertions发生冲突。

    4. 我想得出结论,今天(2017-03-03) AssertJ 的易用性,可发现的API,快速的开发速度以及作为事实上的测试依赖是JDK8的最佳解决方案,无论测试框架如何(JUnit与否),之前的JDK应该依赖于 try - catch 块即使他们感到笨拙。

答案 1 :(得分:5)

如果我不明白你的错,你需要这样的东西:

@Test(expected = NullPointerException.class)
public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();

    verify(calling, never()).method();
    Assert.fail("No NPE");
}

但通过测试“NPENotThrown”的命名,我希望这样的测试:

public void testNPENotThrown {
    Calling calling= Mock(Calling .class);
    testClass.setInner(calling);
    testClass.setThrow(true);

    testClass.testMethod();
    try {
        verify(calling, never()).method();
        Assert.assertTrue(Boolean.TRUE);
    } catch(NullPointerException ex) {
        Assert.fail(ex.getMessage());
    }
}

答案 2 :(得分:2)

另一种方法可能是使用try / catch。它有点乱,但据我所知,这个测试无论如何都将是短暂的,因为它是TDD:

@Test
public void testNPENotThrown{
  Calling calling= Mock(Calling.class);
  testClass.setInner(calling);
  testClass.setThrow(true);

  try{
    testClass.testMethod();
    fail("NPE not thrown");
  }catch (NullPointerException e){
    //expected behaviour
  }
}
编辑:当我写这篇文章时,我很着急。我的意思是'这个测试将是短暂的,因为它对于TDD'是你说你要写一些代码来立即修复这个测试,所以它将来永远不会抛出NullPointerException。然后你也可以删除测试。因此,可能不值得花大量时间编写一个漂亮的测试(因此我的建议: - ))

更一般地说:

从测试开始断言(例如)方法的返回值不为null是已建立的TDD原则,并且检查NullPointerException(NPE)是实现此目的的一种可能方式。但是,您的生产代码可能不会产生NPE抛出的流程。你想要检查null,然后做一些明智的事情,我想。这将使得这个特定的测试在那时是多余的,因为它将检查NPE不会被抛出,而事实上它永远不会发生。然后,您可以使用测试来替换它,该测试验证遇到null时会发生什么:例如,返回NullObject,或者抛出其他类型的异常,无论什么是合适的。

当然没有要求您删除冗余测试,但如果不这样做,它将会在那里使每个构建稍慢,并使每个读取测试的开发人员都不知道; “嗯,一个NPE?这个代码肯定不会抛出一个NPE?”我见过很多TDD代码,其中测试类有很多像这样的冗余测试。如果时间允许,每隔一段时间就要付费一次。

答案 3 :(得分:0)

通常,每个测试用例都使用新实例运行,因此设置实例变量无济于事。因此,如果没有,请将'{​​{1}}'变量设为静态。