如何验证没有抛出异常
在我使用 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
,设置了 Calling
对象和属性,以便该方法将抛出 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 后:使用 AssertJ 或自定义 lambda 来断言 异常 行为.
post-JDK8 : Use AssertJ or custom lambdas to assert exceptional behaviour.
pre-JDK8 :我会推荐旧的好 try
-catch
块.(别忘了在 catch
块之前添加一个 fail()
断言)
pre-JDK8 : I will recommend the old good try
-catch
block. (Don't forget to add a fail()
assertion before the catch
block)
无论是 Junit 4 还是 JUnit 5.
长篇大论
可以自己编写一个自己动手 try
-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).
但是这些方法并不那么优雅,并且不能很好地与其他工具可读性混合.此外,JUnit 工具确实存在一些缺陷.
But these ways are not so elegant and don't mix well readability wise with other tools. Moreover, JUnit tooling does have some pitfalls.
try
-catch
块,您必须围绕测试的行为编写块并在 catch 块中写入断言,这可能很好,但很多发现这种风格打断了测试的阅读流程.此外,您需要在try
块的末尾编写一个Assert.fail
.否则,测试可能会错过断言的一侧;PMD、findbugs 或 Sonar 会发现此类问题.
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 that this style interrupts the reading flow of a test. Also, you need to write anAssert.fail
at the end of thetry
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 this approach is lacking in 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 know 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 from that it may suffer from the same issue as the @Test
way, depending on 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.
另外,请参阅关于 JUnit 的 comment 问题ExpectedException
的作者.JUnit 4.13-beta-2 甚至弃用了这种机制:
Also, see this comment issue on JUnit of the author of ExpectedException
. JUnit 4.13-beta-2 even deprecates this mechanism:
拉取请求 #1519:弃用 ExpectedException
Pull request #1519: Deprecate ExpectedException
方法 Assert.assertThrows 提供了一种更好的方法来验证异常.此外,ExpectedException 的使用在与 TestWatcher 等其他规则一起使用时容易出错,因为在这种情况下规则的顺序很重要.
The method Assert.assertThrows provides a nicer way for verifying exceptions. In addition, the use of ExpectedException is error-prone when used with other rules like TestWatcher because the order of rules is important in that case.
因此,上述这些选项有很多注意事项,并且显然无法避免编码错误.
So these above options have all their load of caveats, and clearly not immune to coder errors.
在创建这个看起来很有希望的答案后,我意识到了一个项目,它是 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 the latter assertion. And you can use any assertion library like Hamcrest or 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();
如您所见,代码非常简单,您在特定行捕获异常,then
API 是一个别名,将使用 AssertJ API(类似于使用 assertThat(ex).hasNoCause()...
).在某些时候,该项目依赖于 AssertJ 的祖先 FEST-Assert.看来该项目正在酝酿对 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 mock maker has different drawbacks that the regular mock maker.
它需要另一个测试依赖项.
It requires yet another test dependency.
一旦库支持 lambda,这些问题将不适用.但是,AssertJ 工具集将复制该功能.
These issues won't apply once the library supports lambdas. However, the functionality will be duplicated by the AssertJ toolset.
综合考虑如果不想使用catch-exception工具,我会推荐try
-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 中,lambda 进入了测试场景,事实证明它们是断言异常行为的一种有趣方式.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 的示例测试:
And a sample test with 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 的近乎完整的重写,断言已经 改进了 一点,它们可能被证明是一种有趣的开箱即用的方式来正确断言异常.但实际上断言 API 还是有点差,除了 assertThrows
.
With a near-complete rewrite of JUnit 5, assertions have been improved a bit, they may prove interesting as an out of the box way to assert properly exception. But really the assertion API is still a bit poor, there's nothing outside 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.
此外,如果您记得与 Matcher
或 Assert
的名称冲突,请准备好与 Assertions
遇到相同的冲突.
Also if you remember name clash with Matcher
or Assert
, be prepared to meet the same clash with Assertions
.
我想总结一下,今天 (2017-03-03) AssertJ 的易用性、可发现的 API、快速的开发速度以及 事实上的em> 测试依赖是 JDK8 的最佳解决方案,不管测试框架(JUnit 与否),之前的 JDK 应该依赖 try
-catch
块,即使他们觉得笨重.
I'd like to conclude that today (2017-03-03) AssertJ's ease of use, discoverable API, the 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.
相关文章