最近在做TWU關於TDD的作業,對JUnit中測試異常拋出的方法進行了一些學習和思考。
在進行單元測試的時候有的時候需要測試某一方法是否拋出了正確的異常。例如,我有一個方法,里面對一個List進行讀取操作,可能會拋出IndexOutOfBoundsException,我希望在單元測試中通過測試保證該方法會正確的拋出正確類型的異常。總結起來這樣的測試異常是否被正確拋出的方法有三種:
1. try…fail...catch…
@Test public voidtestExceptionMessage() { try { new ArrayList<Object>().get(0); fail("Expected an IndexOutOfBoundsException to be thrown"); } catch (IndexOutOfBoundsException anIndexOutOfBoundsException) { assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0")); } }
這種寫法看上去和實現類的寫法很相似,當沒有異常被拋出的時候fail方法會被調用,輸出測試失敗的信息。
2.@Test(expected=xxx)
@Test(expected= IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); }
這種寫法看上去簡單了一些,但是它有一個潛在的問題:當被標記的這個測試方法中的任何一個操作拋出了相應的異常時,這個測試就會通過。這就意味着有可能拋出異常的地方並不是我們期望的那個操作。雖然這種情況可以在寫test case的時候人為的避免,但是還是有更好的方法來測試異常拋出。
3.ExpectedException Rule
@Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldTestExceptionMessage() throws IndexOutOfBoundsException { List<Object> list = new ArrayList<Object>(); thrown.expect(IndexOutOfBoundsException.class); thrown.expectMessage("Index: 0, Size: 0"); list.get(0); // execution will never get past this line }
這種方法除了可以指定期望拋出的異常類型之外還可以指定在拋出異常時希望同時給出的異常信息。它需要在測試之前使用Rule標記來指定一個ExpectedException,並在測試相應操作之前指定期望的Exception類型(如IndexOutOfBoundException.class)
這三種方法都可以做到測試相應的操作是否拋出了期望的異常,但是哪種方法更好更適合使用呢?我的總結是:
@Test(expected=xxx) > 根本不測異常是否正確拋出
ExpectedException > @Test(expected=xxx)
try…fail…catch > ExpectedException
我之所以認為try…fail…catch方法比ExpectedException好是因為:
1. try…fail…catch更符合一般的test function的風格,先進行某項操作,在對結果進行assert。而ExpectedException的順序確實先指明期待的結果再進行相應的操作。
2. 雖然TDD的最佳實踐是每個test function只有一個assert,但是還是在有些情況下會在同一個test function里使用多個assert來對不同的方面進行測試。但是使用ExpectedException進行異常測試后,當前的test function就結束了,如果在expect的相應操作之后還有assert的話會被自動跳過,而try…fail…catch則不會跳出當前test function, 其后面的assert依然會被順序執行。
3. ExpectedException是JUnit提供的,因此在使用別的測試框架時這樣的測試方法無效。而try/catch有更多的測試框架支持(fail也是JUnit提供的。使用別的測試框架的時候不能使用fail來給出測試失敗的信息)