如何验证是否未引发异常

2022-09-02 09:59:43

在我使用Mockito的单元测试中,我想验证它是否没有被抛出。NullPointerException

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

    testClass.testMethod();

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

我的测试设置了 ,设置对象和属性,以便该方法将引发 .testClassCallingNullPointerException

验证了调用.方法()从未被调用。

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

    calling.method();
}

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

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


答案 1

tl;博士

  • 后 JDK8 :使用 AssertJ 或自定义 lambda 来断言异常行为。

  • 在JDK8之前:我会推荐旧的好-块。(不要忘记在 catch 块之前添加一个 fail() 断言trycatch)

无论 Junit 4 还是 JUnit 5。

长篇大论

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

但这些方式并不那么优雅,并且不能与其他工具很好地混合可读性。此外,JUnit工具确实存在一些陷阱。

  1. - 块你必须围绕测试的行为编写块,并在捕获块中写入断言,这可能很好,但许多人发现这种样式中断了测试的读取流。另外,您需要在块的末尾编写一个。否则,测试可能会错过断言的一面;PMDfindbugsSonar将发现此类问题。trycatchAssert.failtry

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

    • 如果测试需要检查异常的其他内容,例如原因或消息(良好的异常消息非常重要,那么拥有精确的异常类型可能还不够)。
    • 此外,由于期望被放置在方法中,根据测试代码的编写方式,测试代码的错误部分可能会引发异常,从而导致假阳性测试,我不确定PMDfindbugsSonar是否会在此类代码上给出提示。

      @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. 该规则也是试图修复前面的警告,但是由于它使用了期望样式,因此使用起来感觉有点尴尬,EasyMock用户非常了解这种样式。这对某些人来说可能很方便,但如果你遵循行为驱动发展(BDD)或安排行为断言(AAA)原则,那么规则将不适合这些写作风格。除此之外,它可能会遇到与方式相同的问题,具体取决于您放置期望的位置。ExpectedExceptionExpectedException@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,它也会破坏您的阅读流程。

    另请参阅 作者 JUnit 上的此注释问题。JUnit 4.13-beta-2 甚至弃用了这种机制:ExpectedException

    拉取请求 #1519:弃用预期异常

    方法 Assert.assertThrows 为验证异常提供了一种更好的方法。此外,当与其他规则(如TestWatcher)一起使用时,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();
    

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

    目前,这个库有两个缺点:

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

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

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

    考虑到所有因素,如果您不想使用捕获异常工具,我将推荐尝试捕获块的旧好方法,至少在JDK7之前。对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是断言异常。

  2. 借助 JDK8,lambda 进入了测试场景,事实证明,它们是断言异常行为的有趣方式。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的几乎完全重写,断言已经得到了一些改进,它们可能会被证明是一种开箱即用的正确异常断言方式。但实际上断言API仍然有点差,断言抛出之外什么都没有。

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

    正如你所注意到的,仍然返回,因此不允许像 AssertJ 这样的链接断言。assertEqualsvoid

    另外,如果您还记得名称冲突与 或 ,请准备好遇到与 .MatcherAssertAssertions

我想总结一下,今天(2017-03-03)AssertJ的易用性,可发现的API,快速的开发速度以及作为事实上的测试依赖关系是JDK8的最佳解决方案,无论测试框架(JUnit与否),以前的JDK应该依靠尝试捕获块,即使它们感觉笨拙。


答案 2

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

@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());
    }
}

推荐