如何validation是否未抛出exception

在我使用Mockito的unit testing中,我想validation是否没有抛出NullPointerException

 public void testNPENotThrown{ Calling calling= Mock(Calling.class); testClass.setInner(calling); testClass.setThrow(true); testClass.testMethod(); verify(calling, never()).method(); } 

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

validation从不调用Calling.method()。

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

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

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

TL;博士

  • 在JDK8之前:我会推荐旧的好trycatch块。

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

长话故事

可以自己编写一个trycatch块或使用JUnit工具( @Test(expected = ...)@Rule ExpectedException JUnit规则function)。

但是这些方式并不那么优雅,并且不能与其他工具很好地兼顾

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

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

    • 如果测试需要检查exception上的其他内容,例如原因或消息(良好的exception消息非常重要,那么具有精确的exception类型可能还不够)。
    • 此外,由于期望放在方法中,取决于测试代码的编写方式,然后测试代码的错误部分可能抛出exception,导致误报测试,我不确定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. 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 } 

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

    另请参阅ExpectedException作者的JUnit上的此注释问题。

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

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

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

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

     // 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(); 

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

    目前这个库有两个缺点:

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

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

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

    如果您不想使用catch-exception工具,请考虑所有因素,我将推荐trycatch块的旧方法,至少在JDK7之前。 对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是声明exception。

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

    并使用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,断言得到了一些改进 ,它们可能被certificate是一种开箱即用的方式来断言正确的exception。 但是真的断言API仍然有点差,断言之外什么也没有。

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

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

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

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

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

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

另一种方法可能是使用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是不会抛出的,而事实上它永远不会发生。 然后,您可以使用测试来替换它,该测试validation遇到null时会发生什么:例如,返回NullObject,或者抛出其他类型的exception,无论什么是合适的。

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

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