問題描述
在我使用 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.
這篇關于如何驗證沒有拋出異常的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!