問(wèn)題描述
在我使用 Mockito 的單元測(cè)試中,我想驗(yàn)證 NullPointerException
沒(méi)有被拋出.
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();
}
我的測(cè)試設(shè)置了 testClass
,設(shè)置了 Calling
對(duì)象和屬性,以便該方法將拋出 NullPointerException
.
My test set up the testClass
, setting the Calling
object and the property so that the method will throw a NullPointerException
.
我驗(yàn)證 Calling.method() 從未被調(diào)用.
I verify that the Calling.method() is never called.
public void testMethod(){
if(throw) {
throw new NullPointerException();
}
calling.method();
}
我想要一個(gè)失敗的測(cè)試,因?yàn)樗鼟伋鲆粋€(gè) NullPointerException
,然后我想編寫(xiě)一些代碼來(lái)解決這個(gè)問(wèn)題.
I want to have a failing test because it throws a NullPointerException
, and then I want to write some code to fix this.
我注意到的是測(cè)試總是通過(guò),因?yàn)闇y(cè)試方法永遠(yuǎn)不會(huì)拋出異常.
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 來(lái)斷言 異常 行為.
post-JDK8 : Use AssertJ or custom lambdas to assert exceptional behaviour.
pre-JDK8 :我會(huì)推薦舊的好 try
-catch
塊.(別忘了在 catch
塊之前添加一個(gè) fail()
斷言)
pre-JDK8 : I will recommend the old good try
-catch
block. (Don't forget to add a fail()
assertion before the catch
block)
無(wú)論是 Junit 4 還是 JUnit 5.
長(zhǎng)篇大論
可以自己編寫(xiě)一個(gè)自己動(dòng)手 try
-catch
塊或使用JUnit 工具(@Test(expected = ...)
或 @Rule ExpectedException
JUnit 規(guī)則特性).
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).
但是這些方法并不那么優(yōu)雅,并且不能很好地與其他工具可讀性混合.此外,JUnit 工具確實(shí)存在一些缺陷.
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
塊,您必須圍繞測(cè)試的行為編寫(xiě)塊并在 catch 塊中寫(xiě)入斷言,這可能很好,但很多發(fā)現(xiàn)這種風(fēng)格打斷了測(cè)試的閱讀流程.此外,您需要在try
塊的末尾編寫(xiě)一個(gè)Assert.fail
.否則,測(cè)試可能會(huì)錯(cuò)過(guò)斷言的一側(cè);PMD、findbugs 或 Sonar 會(huì)發(fā)現(xiàn)此類(lèi)問(wèn)題.
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 = ...)
功能很有趣,因?yàn)槟梢跃帉?xiě)更少的代碼,然后編寫(xiě)此測(cè)試據(jù)說(shuō)不太容易出現(xiàn)編碼錯(cuò)誤.但是這種方法在某些領(lǐng)域是缺乏的.
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.
- 如果測(cè)試需要檢查異常的其他內(nèi)容,例如原因或消息(好的異常消息非常重要,僅擁有精確的異常類(lèi)型可能還不夠).
同樣由于期望被放置在方法中,根據(jù)測(cè)試代碼的編寫(xiě)方式,測(cè)試代碼的錯(cuò)誤部分可能會(huì)引發(fā)異常,導(dǎo)致誤報(bào)測(cè)試,我不確定PMD、findbugs 或 Sonar 將對(duì)此類(lèi)代碼提供提示.
- 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
規(guī)則也是嘗試修復(fù)之前的警告,但使用起來(lái)感覺(jué)有點(diǎn)別扭,因?yàn)樗褂昧似谕麡邮剑?em>EasyMock 用戶(hù)非常了解這種風(fēng)格.這對(duì)某些人來(lái)說(shuō)可能很方便,但如果您遵循 行為驅(qū)動(dòng)開(kāi)發(fā) (BDD) 或 安排行為斷言 (AAA) 原則,則 ExpectedException
規(guī)則獲勝不適合那些寫(xiě)作風(fēng)格.除此之外,它可能會(huì)遇到與 @Test
方式相同的問(wèn)題,具體取決于您將期望放在哪里.
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
}
即使預(yù)期的異常放在測(cè)試語(yǔ)句之前,如果測(cè)試遵循 BDD 或 AAA,它也會(huì)破壞您的閱讀流程.
Even the expected exception is placed before the test statement, it breaks your reading flow if the tests follow BDD or AAA.
另外,請(qǐng)參閱關(guān)于 JUnit 的 comment 問(wèn)題ExpectedException
的作者.JUnit 4.13-beta-2 甚至棄用了這種機(jī)制:
Also, see this comment issue on JUnit of the author of ExpectedException
. JUnit 4.13-beta-2 even deprecates this mechanism:
拉取請(qǐng)求 #1519:棄用 ExpectedException
Pull request #1519: Deprecate ExpectedException
方法 Assert.assertThrows 提供了一種更好的方法來(lái)驗(yàn)證異常.此外,ExpectedException 的使用在與 TestWatcher 等其他規(guī)則一起使用時(shí)容易出錯(cuò),因?yàn)樵谶@種情況下規(guī)則的順序很重要.
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.
因此,上述這些選項(xiàng)有很多注意事項(xiàng),并且顯然無(wú)法避免編碼錯(cuò)誤.
So these above options have all their load of caveats, and clearly not immune to coder errors.
在創(chuàng)建這個(gè)看起來(lái)很有希望的答案后,我意識(shí)到了一個(gè)項(xiàng)目,它是 catch-exception.
正如該項(xiàng)目的描述所說(shuō),它讓編碼人員編寫(xiě)流暢的代碼行來(lái)捕獲異常,并為后面的斷言提供這個(gè)異常.您可以使用任何斷言庫(kù),例如 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.
取自主頁(yè)的快速示例:
// 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();
如您所見(jiàn),代碼非常簡(jiǎn)單,您在特定行捕獲異常,then
API 是一個(gè)別名,將使用 AssertJ API(類(lèi)似于使用 assertThat(ex).hasNoCause()...
).在某些時(shí)候,該項(xiàng)目依賴(lài)于 AssertJ 的祖先 FEST-Assert.看來(lái)該項(xiàng)目正在醞釀對(duì) 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.
目前,這個(gè)庫(kù)有兩個(gè)缺點(diǎn):
Currently, this library has two shortcomings :
在撰寫(xiě)本文時(shí),值得注意的是,該庫(kù)基于 Mockito 1.x,因?yàn)樗诤笈_(tái)創(chuàng)建了測(cè)試對(duì)象的模擬.由于 Mockito 仍未更新 此庫(kù)不能與最終類(lèi)或最終方法一起使用.即使它基于當(dāng)前版本的 Mockito 2,這也需要聲明一個(gè)全局模擬制造商 (
inline-mock-maker
),這可能不是你想要的,作為這個(gè)模擬制造商與普通的模擬制造商有不同的缺點(diǎn).
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.
它需要另一個(gè)測(cè)試依賴(lài)項(xiàng).
It requires yet another test dependency.
一旦庫(kù)支持 lambda,這些問(wèn)題將不適用.但是,AssertJ 工具集將復(fù)制該功能.
These issues won't apply once the library supports lambdas. However, the functionality will be duplicated by the AssertJ toolset.
綜合考慮如果不想使用catch-exception工具,我會(huì)推薦try
-catch
這個(gè)老好辦法塊,至少到JDK7.對(duì)于 JDK 8 用戶(hù),您可能更喜歡使用 AssertJ,因?yàn)樗峁┑目赡懿粌H僅是斷言異常.
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 進(jìn)入了測(cè)試場(chǎng)景,事實(shí)證明它們是斷言異常行為的一種有趣方式.AssertJ 已更新,提供了一個(gè)很好的流暢 API 來(lái)斷言異常行為.
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.
還有一個(gè)帶有 AssertJ 的示例測(cè)試:
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");
}
隨著對(duì) JUnit 5 的近乎完整的重寫(xiě),斷言已經(jīng) 改進(jìn)了 一點(diǎn),它們可能被證明是一種有趣的開(kāi)箱即用的方式來(lái)正確斷言異常.但實(shí)際上斷言 API 還是有點(diǎn)差,除了 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
的名稱(chēng)沖突,請(qǐng)準(zhǔn)備好與 Assertions
遇到相同的沖突.
Also if you remember name clash with Matcher
or Assert
, be prepared to meet the same clash with Assertions
.
我想總結(jié)一下,今天 (2017-03-03) AssertJ 的易用性、可發(fā)現(xiàn)的 API、快速的開(kāi)發(fā)速度以及 事實(shí)上的em> 測(cè)試依賴(lài)是 JDK8 的最佳解決方案,不管測(cè)試框架(JUnit 與否),之前的 JDK 應(yīng)該依賴(lài) try
-catch
塊,即使他們覺(jué)得笨重.
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.
這篇關(guān)于如何驗(yàn)證沒(méi)有拋出異常的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!