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,然后我想写一些代码来解决这个问题.
我注意到的是,测试总是通过,因为异常从来没有被抛出测试方法.
解决方法
> pre-JDK8:我会推荐旧的好的try-catch块.
> post-JDK8:使用AssertJ或custom lambdas来表示异常行为.
漫长的故事
可以自己写一个自己尝试catch块或使用JUnit工具(@Test(expected = …)或@Rule ExpectedException JUnit规则功能).
但是这些方式并不那么优雅,并不能很好地与其他工具混合良好的可读性.
> try-catch块你必须围绕被测试的行为写入块,并在断块中写入断言,可能很好,但是很多查找taht这种风格会中断测试的读取流程.此外,您需要在try块的末尾写一个Assert.fail,否则测试可能会错过该断言的一边; PMD,findbugs或Sonar将会发现这样的问题.
> @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 }
即使预期的异常放在测试语句之前,如果测试遵循BDD或AAA,则会破坏您的阅读流程.
另请参阅这个comment问题在JUnit上的ExpectedException的作者.
所以这些上面的选项都有它们所有的注意事项,显然不能免除编码器的错误.
>在创建这个看起来很有前途的答案后,我发现了一个项目,这是catch-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();
正如你可以看到代码真的很简单,你可以捕获特定行上的异常,然后API是一个别名,它将使用AssertJ API(类似于使用assertThat(ex).hasNoCause()…)).在某些时候,项目依靠FEST-Assert AssertJ的祖先.编辑:似乎该项目正在酝酿Java 8 Lambdas支持.
目前这个图书馆有两个缺点:
>在撰写本文时,值得注意的是,这个库基于Mockito 1.x,因为它在场景后面创建了被测对象的模拟.由于Mockito尚未更新,因此该库无法使用最终类或最终方法.即使它是基于当前版本的mockito 2,这将需要声明一个全球模拟制造商(内联制造商),这可能不是你想要的,因为这个模拟器具有不同的缺点,即常规模拟器.
>它需要另一个测试依赖.
一旦图书馆支持lambdas,这些问题就不会适用,但AssertJ工具集将会复制这些功能.
考虑到如果你不想使用catch-exception工具,我会推荐try-catch块的旧的好方法,至少到JDK7.对于JDK 8用户,您可能更喜欢使用AssertJ,因为它可能不仅仅是断言异常.
>使用JDK8,羊羔进入测试场景,它们被证明是一种有趣的方式来证明异常行为. 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"); }
>随着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.
此外,如果您记住与Matcher或Assert的名称冲突,请准备与Assertions相同的冲突.
我想得出结论,今天(2017-03-03)AssertJ的易用性,可发现的API,快速的开发速度和事实上的测试依赖性是JDK8的最佳解决方案,无论测试框架如何(JUnit或不是)因此,以前的JDK应该依靠try-catch块,即使他们觉得笨重.