JUnit 4 如何正確測試異常


本篇講述如何在 JUnit 4 下正確測試異常,我會從 try..catch 的方式談起,然后說到 @Test(expected=Exception.class), 最后論及 @Rules public ExpectedException 的實現方式,最終基本可確定用 @Rules 是最方便的。

我們在用 JUnit 測試方法異常的時候,最容易想到的辦法就是用 try...catch 去捕獲異常,需要斷言以下幾個條件:

1. 確實拋出的異常
2. 拋出異常的 Class 類型
3. 拋出異常的具體類型,一般檢查異常的 message 屬性中包含的字符串的斷定

所以常用的代碼你可能會這么寫:

@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.")

代碼行,不然如果被測試的方法如果沒有拋出異常的話,這個用例是通過的,而你預期的是要拋出異常的。

上面的土辦法對於哪個 JUnit 版本是是適合,可是我們早已步入了 JUnit 4 的時候,大可不必如此這般的去測試方法異常。雖然這樣也能測定出是否執行出預期的異常來,但它仍有弊端,接下來會一對比就知道了,try...catch 的方法,JUnit 無法為你提示出詳細的斷言失敗原因。

那么來看看自從 JUnit 4 后可以怎么去測試異常呢?用 @Test(execpted=Exception.class) 注解就行,參考如下代碼:

@Test(expected = NullPointerException.class)
public void passwordIsNullThrowsException() throws InvalidPasswordException {
    Password.validate(null);
}

如果被測試的方法有拋出 NullPointerException 類型便是斷言成功,對了 @Test(expected = NullPointerException.class) 只能判斷出異常的類型,並無相應的注解能斷言出異常的更具體的信息,即無法判定拋出異常的  message 屬性。

那么,有時候我們會在一個方法中多次拋出一種類型的異常,但原因不同,即異常的 message 信息不同,比如出現 InvalidPasswordException 時會有以下兩種異常:

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 的寫法了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM