如何验证是否未抛出异常 [英] How to verify that an exception was not thrown

查看:84
本文介绍了如何验证是否未抛出异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my unit test using Mockito I want to verify that NullPointerException was not thrown.

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

    testClass.testMethod();

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

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

My test set up the testClass, setting the Calling object and the property so that the method will throw a NullPointerException.

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

I verify that the Calling.method() is never called.

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

    calling.method();
}

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

I want to have a failing test because it throws a NullPointerException, and then I want to write some code to fix this.

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

What I have noticed is that the test always passes as the exception is never thrown up the the test method.

推荐答案

tl; dr


  • JDK8之前:我会推荐旧的尝试 - catch 块。

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

post-JDK8 : Use AssertJ or custom lambdas to assert exceptional behaviour.

长篇故事

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

It is possible to write yourself a do it yourself try-catch block or use the JUnit tools (@Test(expected = ...) or the @Rule ExpectedException JUnit rule feature).

但是这些方式并不那么优雅,并且不能与其他工具很好地混合可读性

But these way are not so elegant and don't mix well readability wise with other tools.


  1. 尝试 - catch 阻止你必须围绕测试行为编写块,然后编写在catch块中断言,这可能很好,但很多人发现这种风格会中断测试的读取流程。你还需要在尝试块的末尾写一个 Assert.fail ,否则测试可能会遗漏断言; PMD findbugs Sonar 会发现此类问题。

  1. The try-catch block you have to write the block around the tested behavior, and write the assertion in the catch block, that may be fine but many find taht this style interrupts the reading flow of a test. Also you need to write an Assert.fail at the end of the try block otherwise the test may miss one side of the assertions ; PMD, findbugs or Sonar will spot such issues.

@Test(expected = ...)功能很有意思,因为你可以编写更少的代码然后编写这个测试据说不太容易出现编码错误。 这种方法缺少一些区域。

The @Test(expected = ...) feature is interesting as you can write less code and then writing this test is supposedly less prone to coding errors. But ths approach is lacking a some areas.


  • 如果测试需要检查异常上的其他内容,例如原因或消息(良好的异常消息非常重要,具有精确的异常类型可能还不够)。

  • 同样由于期望放在方法中,取决于测试代码的编写方式,然后测试代码的错误部分会抛出异常,导致误报测试,我不确定 PMD findbugs Sonar 会给出这些代码的提示。

  • If the test needs to check additional things on the exception like the cause or the message (good exception messages are really important, having a precise exception type may not be enough).
  • Also as the expectation is placed around in the method, depending on how the tested code is written then the wrong part of the test code can throw the exception, leading to false positive test and I m not sure that PMD, findbugs or Sonar will give hints on such code.

@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 方式相同的问题,具体取决于您预期的位置。

The ExpectedException rule is also an attempt to fix the previous caveats, but it feels a bit awkward to use as it uses an expectation style, EasyMock users knows very well this style. It might be convenient for some, but if you follow Behaviour Driven Development (BDD) or Arrange Act Assert (AAA) principles the ExpectedException rule won't fit in those writing style. Aside of that it may suffer from the same issue as the as the @Test way, depending where you place the expectation.

@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,它会打破您的阅读流程。

Even the expected exception is placed before the test statement, it breaks your reading flow if the tests follow BDD or AAA.

另见 ExpectedException 的作者JUnit上的rel =noreferrer>评论问题。

Also see this comment issue on JUnit of the author of ExpectedException.

所以这些上面的选项都有很多警告,显然不能免于编码器错误。

So these above options have all their load of caveats, and clearly not immune to coder errors.


  1. 在创建这个看起来很有希望的答案后,我发现了一个项目,它是 catch-exception

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

As the description of the project says, it let a coder write in a fluent line of code catching the exception and offer this exception for later assertion. And you can use any assertion library like Hamcrest or AssertJ.

从主页获取的快速示例:

A rapid example taken from the home page :

// 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支持。

As you can see the code is really straightforward, you catch the exception on a specific line, the then API is an alias that will use AssertJ APIs (similar to using assertThat(ex).hasNoCause()...). At some point the project relied on FEST-Assert the ancestor of AssertJ. It seems the project is brewing a Java 8 Lambdas support.

目前这个库有两个缺点:

Currently this library has two shortcomings :


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

  • At the time of this writing it is noteworthy to say this library is based on Mockito 1.x as it creates a mock of the tested object behind the scene. As Mockito is still not updated this library cannot work with final classes or final methods. And even if it was based on mockito 2 in the current version, this would require to declare a global mock maker (inline-mock-maker), something that may not what you want, as this mockmaker has different drawbacks that the regular mockmaker.

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

It requires yet another test dependency.

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

These issues won't apply once the library will support lambdas, however the functionality will be duplicated by AssertJ toolset.

如果您不想使用catch-exception工具,请考虑所有因素,我会推荐尝试的旧方法 - catch 阻止,至少达到JDK7。对于JDK 8用户,您可能更喜欢使用AssertJ,因为它提供的可能不仅仅是断言异常。

Taking all into account if you don't want to use the catch-exception tool, I will recommend the old good way of the try-catch block, at least up to the JDK7. And for JDK 8 users you might prefer to use AssertJ as it offers may more than just asserting exceptions.

使用JDK8,lambdas进入测试场景,他们已被证明是一种有趣的方式来断言特殊行为。 AssertJ已经更新,提供了一个很好的流畅API来断言异常行为。

With the JDK8, lambdas enter the test scene, and they have proved to be an interesting way to assert exceptional behaviour. AssertJ has been updated to provide a nice fluent API to assert exceptional behaviour.

并且使用 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,断言已经< a href =http://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions =noreferrer>改进了一下,他们可能会被证明是有趣的用于断言正确异常的方法。但实际上断言API仍然有点差,除了 assertThrows

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

    您注意到 assertEquals 仍然返回 void ,因此不允许链接断言,如AssertJ。

    As you noticed assertEquals is still returning void, and as such doesn't allow chaining assertions like AssertJ.

    此外,如果你还记得与匹配断言的名称冲突,准备好与断言相同的冲突

    Also if you remember name clash with Matcher or Assert, be prepared to meet the same clash with Assertions.

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

    I'd like to conclude that today (2017-03-03) AssertJ's ease of use, discoverable API, rapid pace of development and as a de facto test dependency is the best solution with JDK8 regardless of the test framework (JUnit or not), prior JDKs should instead rely on try-catch blocks even if they feel clunky.

    这篇关于如何验证是否未抛出异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆