Java 如何验证未引发异常

Java 如何验证未引发异常,java,mocking,mockito,powermock,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();

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

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

    testClass.testMethod();

    verify(calling, never()).method();
}
我的测试设置了
testClass
,设置了
调用
对象和属性,以便方法抛出
NullPointerException

I验证从未调用调用的.method()

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

    calling.method();
}
我希望有一个失败的测试,因为它抛出了一个
NullPointerException
,然后我想编写一些代码来修复这个问题


我注意到,测试总是通过,因为测试方法从未抛出异常。

如果我不理解你的错误,你需要这样的东西:

@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");
}
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());
    }
}
但是,通过将测试命名为“NPENotThrown”,我希望有这样一个测试:

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

通常,每个测试用例都会运行一个新实例,因此设置实例变量没有帮助。因此,如果不是静态的,则将“
throw
”变量设为静态。

另一种方法可能是使用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时会发生什么:例如,返回一个null对象,或者抛出一些其他类型的异常,只要合适

当然,没有要求您删除冗余测试,但是如果您不删除,它将使每个构建稍微慢一点,并使每个阅读测试的开发人员感到疑惑;“嗯,NPE?这个代码肯定不能抛出NPE吗?”。我见过很多TDD代码,其中测试类有很多这样的冗余测试。如果时间允许,每隔一段时间检查一下你的测试是值得的。

tl;dr

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

  • JDK8之前的版本:我将推荐旧的good
    try
    -
    catch
    块。(不要忘记在
    catch
    块之前添加
    fail()
    断言)

不管是JUnit4还是JUnit5

长话短说

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

但是这些方法并不是很优雅,也不能与其他工具很好地结合在一起。此外,JUnit工具确实存在一些缺陷

  • try
    -
    catch
    块您必须围绕测试的行为编写块,并在catch块中编写断言,这可能没问题,但许多人发现这种样式会中断测试的读取流。另外,您需要在
    try
    块的末尾编写一个
    Assert.fail
    。否则,测试可能会遗漏断言的一面;PMD、findbugs或声纳将发现此类问题

  • @Test(expected=…)
    特性很有趣,因为您可以编写更少的代码,而编写此测试据说不太容易出现编码错误但是这种方法在某些领域缺乏

    • 如果测试需要检查异常的其他内容,如原因或消息(好的异常消息非常重要,拥有精确的异常类型可能不够)
    • 另外,由于方法中存在期望值,根据测试代码的编写方式,测试代码的错误部分可能引发异常,导致测试呈假阳性,我不确定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
      }
      
  • 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
    }
    
    即使预期的异常放在test语句之前,如果测试遵循BDD或AAA,它也会中断您的读取流

    另外,请参阅
    ExpectedException
    作者JUnit上的本期文章。甚至反对这种机制:

    :不推荐ExpectedException

    Assert.assertThrows方法为验证异常提供了更好的方法。此外,当与其他规则(如TestWatcher)一起使用时,ExpectedException的使用很容易出错,因为在这种情况下规则的顺序很重要

  • 因此,以上这些选项都有大量的警告,显然不能避免编码错误

  • 在创建了这个答案之后,我意识到有一个项目看起来很有希望,它是

    作为项目sa的说明
    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }