如何验证未抛出异常

wel*_*l_i 16 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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

    calling.method();
}
Run Code Online (Sandbox Code Playgroud)

我想要一个失败的测试,因为它抛出一个NullPointerException,然后我想写一些代码来解决这个问题.

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

Bri*_*ice 16

TL;博士

  • 预JDK8:我会建议老好try- catch块.

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

长话故事

这是可能自己写一个自己做的 try - catch块或使用JUnit工具(@Test(expected = ...)@Rule ExpectedExceptionJUnit的规则功能).

但是这些方式并不那么优雅,并且不能将可读性与其他工具完美结合在一起.

  1. try- catch块,你必须写周围的测试行为块,并写在catch块的断言,这可能是罚款,但很多人发现taht这种风格中断测试的阅读流程.你还需要Assert.failtry块的末尾写一个,否则测试可能会错过断言的一面; PMD,findbugsSonar将发现此类问题.

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

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

      @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
      }
      
      Run Code Online (Sandbox Code Playgroud)
  3. ExpectedException规则也是尝试修复之前的警告,但使用期望风格使用感觉有点尴尬,EasyMock用户非常清楚这种风格.对某些人来说可能很方便,但如果您遵循行为驱动开发(BDD)或安排行为断言(AAA)原则,则该ExpectedException规则将不适合这些写作风格.除此之外,它可能会遇到与问题相同的问题@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
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    另请参阅作者JUnit上的此评论问题ExpectedException.

所以这些上面的选项都有他们的注意事项,显然不能免于编码器错误.

  1. 在创建了这个看起来很有前途的答案后,我发现了一个项目,这是一个例外.

    正如项目描述所说的那样,它让编码器在流畅的代码行中编写代码来捕获异常并为以后的断言提供此异常.您可以使用任何断言库,如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();
    
    Run Code Online (Sandbox Code Playgroud)

    正如您所看到的,代码非常简单,您可以在特定行上捕获异常,thenAPI是将使用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- catchblock 的旧方法,至少是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");
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 通过几乎完全重写JUnit 5,断言得到了一点改进,它们可能被证明是一种开箱即用的方式来断言正确的异常.但实际上断言API仍然有点差,外面什么都没有assertThrows.

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

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

    此外,如果你记得名字冲突MatcherAssert,准备遇到同样的冲突Assertions.

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


Ste*_*ike 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");
}
Run Code Online (Sandbox Code Playgroud)

但是通过测试"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());
    }
}
Run Code Online (Sandbox Code Playgroud)