咱們在用 JUnit 測試方法異常的時候,最容易想到的辦法就是用 try...catch 去捕獲異常,須要斷言如下幾個條件:java
1. 確實拋出的異常
2. 拋出異常的 Class 類型
3. 拋出異常的具體類型,通常檢查異常的 message 屬性中包含的字符串的判定正則表達式
因此經常使用的代碼你可能會這麼寫:less
@Test public void passwordLengthLessThan6LettersThrowsException(){ try{ Password.validate("123"); fail("No exception thrown."); }catch(Exception ex){ assertTrue(ex instanceof InvalidPasswordException); assertTrue(ex.getMessage().contains("contains at least 6")); } }
這裏被測試的方法是 Password.validate() 方法是否拋出了相應的異常,注意這裏別漏 try 中的測試
fail("No Exception thrown.")ui
代碼行,否則若是被測試的方法若是沒有拋出異常的話,這個用例是經過的,而你預期的是要拋出異常的。lua
上面的土辦法對於哪一個 JUnit 版本是是適合,但是咱們早已步入了 JUnit 4 的時候,大可沒必要如此這般的去測試方法異常。雖然這樣也能測定出是否執行出預期的異常來,但它仍有弊端,接下來會一對比就知道了,try...catch 的方法,JUnit 沒法爲你提示出詳細的斷言失敗緣由。spa
那麼來看看自從 JUnit 4 後能夠怎麼去測試異常呢?用 @Test(execpted=Exception.class) 註解就行,參考以下代碼:ssr
@Test(expected = NullPointerException.class) public void passwordIsNullThrowsException() throws InvalidPasswordException { Password.validate(null); }
若是被測試的方法有拋出 NullPointerException 類型即是斷言成功,對了 @Test(expected = NullPointerException.class) 只能判斷出異常的類型,並沒有相應的註解能斷言出異常的更具體的信息,即沒法斷定拋出異常的 message 屬性。code
那麼,有時候咱們會在一個方法中屢次拋出一種類型的異常,但緣由不一樣,即異常的 message 信息不一樣,好比出現 InvalidPasswordException 時會有如下兩種異常:blog
new InvalidPasswordException("Password must contains at least 6 letters.")
new InvalidPasswordException("Password length less than 15 letters")
這就要有辦法去斷言異常的 message 了,針對於此,自 JUnit 4.7 以後又給了咱們更完美的選擇,就是下面的代碼:
@Rule public ExpectedException expectedEx = ExpectedException.none(); @Test public void passwordIsEmptyThrowsException() throws InvalidPasswordException { expectedEx.expect(InvalidPassrdException.class); expectedEx.expectMessage("required"); Password.validate(""); }
上面代碼需重點關注幾個:
1. @Rule 註解的 ExpectedException 變量聲明,它必須爲 public
2. @Test 處,不能寫成 @Test(expected=InvalidPasswordException.class),不然不能正確測試,也就是
@Test(expected=InvalidPasswordException.class) 和測試方法中的 expectedEx.expectXxx() 方法是不能同時並存的
3. expectedEx.expectMessage() 中的參數是 Matcher 或 subString,就是說可用正則表達式斷定,或判斷是否包含某個子字符串
4. 再就是有一點很重,把被測試方法寫在 expectedEx.expectXxx() 方法後面,否則也不能正確測試的異常
5. 最後一個是,只要測試方法直接拋出被測試方法的異常便可,並不影響你所關心的異常
前面說到用 try...catch 的辦法也能正確測試到異常,@Test(expected=...) 或 @Rule 與 try...catch 的方法對比有什麼好處呢,顯然用 JUnit 4 推薦的方法簡潔明瞭。再來看測試失敗時 JUnit 會爲你提示什麼呢?
try...catch 測試異常失敗時,獲得的提示:
無異常時:
java.lang.AssertionError: No exception thrown.
at org.junit.Assert.fail(Assert.java:91)
at cc.unmi.PasswordTest.passwordLengthLessThan6LettersThrowsException(PasswordTest.java:20)
異常類型不對或異常的 message 不對時:
java.lang.AssertionError:
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.assertTrue(Assert.java:43)
at org.junit.Assert.assertTrue(Assert.java:54)
at cc.unmi.PasswordTest.passwordLengthLessThan6LettersThrowsException(PasswordTest.java:22)
上面能提供給咱們的定位錯誤的幫助不是特別大
再看 @Test(expected=InvalidPasswordException.class) 時測試失敗時的提示:
java.lang.AssertionError: Expected exception: cc.unmi.InvalidPasswordException
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:32)
at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:110)
用 @Rules ExpectedException方式來測試異常,失敗時的提示:
java.lang.AssertionError:
Expected: (exception with message a string containing "YES. required" and an instance of java.lang.NullPointerException)
got: <cc.unmi.InvalidPasswordException: Password is required.>
at org.junit.Assert.assertThat(Assert.java:778)
at org.junit.Assert.assertThat(Assert.java:736)
at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:114)
特別是 @Rules ExpectedException 方法時爲什麼測試失敗提示的清清楚楚。指望什麼異常,異常 message 中含何字符串,實際上確獲得什麼類型的異常,異常中 message 是什麼。有了這,你一看到就知道怎麼去修補你的程序。
因此到了這裏,咱們真的沒理由不用 @Test(expected=...) 或 @Rule ExpectedException 的寫法了。