如何验证没有抛出异常
Posted
技术标签:
【中文标题】如何验证没有抛出异常【英文标题】:How to verify that an exception was not thrown 【发布时间】:2013-06-29 20:43:24 【问题描述】:在我使用 Mockito 的单元测试中,我想验证 NullPointerException
没有被抛出。
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
,然后我想编写一些代码来解决这个问题。
我注意到的是,测试总是通过,因为测试方法永远不会抛出异常。
【问题讨论】:
【参考方案1】:tl;dr
JDK8 后:使用 AssertJ 或自定义 lambdas 来断言异常行为。
pre-JDK8 :我会推荐旧的好 try
-catch
块。 (别忘了在 catch
块之前添加一个 fail()
断言)
无论是 Junit 4 还是 JUnit 5。
长篇大论
可以自己编写一个自己动手 try
-catch
块或使用 JUnit 工具(@Test(expected = ...)
或 @Rule ExpectedException
JUnit 规则功能)。
但是这些方法并不那么优雅,并且不能很好地与其他工具可读性混合。此外,JUnit 工具确实存在一些缺陷。
-
1234563测试。此外,您需要在
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,它也会破坏您的阅读流程。
另外,请参阅ExpectedException
的作者在 JUnit 上的这个 comment 问题。 JUnit 4.13-beta-2 甚至不赞成这种机制:
Pull request #1519: 弃用 ExpectedException
方法 Assert.assertThrows 提供了一种更好的方法来验证异常。此外,ExpectedException 的使用在与 TestWatcher 等其他规则一起使用时容易出错,因为在这种情况下规则的顺序很重要。
因此,上述这些选项有很多注意事项,并且显然无法避免编码错误。
在创建这个看起来很有希望的答案后,我意识到了一个项目,它是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();
您可以看到代码非常简单,您可以在特定行捕获异常,then
API 是一个别名,它将使用 AssertJ API(类似于使用 assertThat(ex).hasNoCause()...
)。 在某些时候,该项目依赖于 AssertJ 的祖先 FEST-Assert。 编辑:看来该项目正在酝酿对 Java 8 Lambdas 的支持。
目前,这个库有两个缺点:
在撰写本文时,值得注意的是,该库基于 Mockito 1.x,因为它在场景后面创建了测试对象的模拟。由于 Mockito 仍未更新 此库不能与最终类或最终方法一起使用。即使它基于当前版本的 Mockito 2,这也需要声明一个全局模拟制造商 (inline-mock-maker
),这可能不是您想要的,因为这个模拟制造商与常规模拟制造商有不同的缺点。
它需要另一个测试依赖项。
一旦库支持 lambda,这些问题将不适用。但是,AssertJ 工具集将复制该功能。
综合考虑如果不想使用catch-exception工具,我会推荐try
-catch
块的老好办法,至少到JDK7。对于 JDK 8 用户,您可能更喜欢使用 AssertJ,因为它提供的可能不仅仅是断言异常。
在 JDK8 中,lambda 进入了测试场景,事实证明它们是一种断言异常行为的有趣方式。 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、快速的开发速度以及 事实上的 em> 测试依赖是 JDK8 的最佳解决方案,无论测试框架如何(JUnit 与否),以前的 JDK 应该改为依赖 try
-catch
块,即使他们觉得笨重。 p>
【讨论】:
【参考方案2】:如果我不理解你的意思,你需要这样的东西:
@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());
【讨论】:
我认为你不需要 Assert.fail("No NPE");因为您已经在第一个块中有注释。如果没有抛出 NullPointer,注释将失败。【参考方案3】:另一种方法可能是改用 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 时会发生什么:例如,返回 NullObject,或抛出一些其他类型的异常,只要合适。
当然没有要求你删除多余的测试,但如果你不这样做,它会坐在那里使每个构建稍微慢一些,并让每个阅读测试的开发人员都感到疑惑; “嗯,一个 NPE?这个代码肯定不能抛出一个 NPE?”。我见过很多 TDD 代码,其中测试类有很多像这样的冗余测试。如果时间允许,每隔一段时间检查一次测试是值得的。
【讨论】:
【参考方案4】:通常每个测试用例都使用一个新实例运行,因此设置实例变量无济于事。因此,如果不是,请将 'throw
' 变量设为静态。
【讨论】:
以上是关于如何验证没有抛出异常的主要内容,如果未能解决你的问题,请参考以下文章