Problem
為了讓程式夠強健,在發生能夠處理的例外情況可能會做retry。在搞笑談軟工有提到retry好的做法,也可以透過spring的RetryTemplate去實做retry(很大包..)。
但在測試的時候,要怎麼確認你的retry是否可以work呢? 讓我們來看看範例。這個範例在執行時,如果有例外產生的時候,會使用替代方案執行,如果超過重試次數就會拋出一個RuntimeException。
package org.tonylin.powermock; public class RetryExample { public User readUser(){ int attempt = 0; int maxAttempt = 2; boolean retry = false; User user = null; do { try { retry = false; if (attempt == 0) user = DaoUtil.readUserFromDB(); else user = DaoUtil.readUserFromFile(); } catch (Exception e) { attempt++; retry = true; if (attempt > maxAttempt) throw new RuntimeException(e); } } while (attempt <= maxAttempt && retry); return user; } }
測試案例可以這樣設計:
- 第一次執行成功。
- 第一次失敗,改由readUserFromFile執行成功。
- 第一次執行失敗,第二次執行readUserFromFile失敗,第三次才成功。
- 執行皆失敗。
該如何透過PowerMock去實做這些測試案例呢?
How to resolve?
第一次執行成功這個案例較基本,我就不特別說明。
宣告
首先我們要先宣告PowerMock相關的annotation:
@RunWith(PowerMockRunner.class) @PrepareForTest({RetryExample.class, DaoUtil.class}) public class TestRetryExample { }
第一次失敗,改由readUserFromFile執行成功
這個案例不難實做,首先讓readUserFromDB拋exception,readUserFromFile回傳User物件。需要驗證的內容,包含取得的物件必須與我們建立的mock物件吻合,還要確認程式先執行readUserFromDB再執行readUserFromFile。因此我在mock DaoUtil時,使用了mockStaticStrict,這會讓PowerMock.verifyAll()驗證流程必須依照我們所宣告的順序。
@Test public void testReadUser_failureAtFirstTime(){ User mockUser = PowerMock.createMock(User.class); PowerMock.mockStaticStrict(DaoUtil.class); DaoUtil.readUserFromDB(); PowerMock.expectLastCall().andThrow(new RuntimeException()).once(); DaoUtil.readUserFromFile(); PowerMock.expectLastCall().andReturn(mockUser).once(); PowerMock.replayAll(); RetryExample retryExample = new RetryExample(); User user = retryExample.readUser(); assertEquals(mockUser, user); PowerMock.verifyAll(); }
執行皆失敗
即所有的請求都拋exception。在驗證的部分要去catch exception,如果沒發生exception就代表有問題。
@Test public void testReadUser_failure3Times(){ PowerMock.mockStaticStrict(DaoUtil.class); DaoUtil.readUserFromDB(); PowerMock.expectLastCall().andThrow(new RuntimeException()).once(); DaoUtil.readUserFromFile(); PowerMock.expectLastCall().andThrow(new RuntimeException()).times(2); PowerMock.replayAll(); RetryExample retryExample = new RetryExample(); try { retryExample.readUser(); fail(); } catch( Exception e ){ PowerMock.verifyAll(); } }
第一次執行失敗,第二次執行readUserFromFile失敗,第三次才成功
這才是這篇的重點。為了能模擬同一個method會有不同的執行結果,我使用了PowerMock.expectLastCall().andAnswer()並且override answer()。接著透過一個flag去記錄是第幾次執行以決定要回傳的結果。
@Test public void testReadUser_failure2Times(){ final User mockUser = PowerMock.createMock(User.class); PowerMock.mockStaticStrict(DaoUtil.class); DaoUtil.readUserFromDB(); PowerMock.expectLastCall().andThrow(new RuntimeException()).once(); DaoUtil.readUserFromFile(); PowerMock.expectLastCall().andAnswer(new IAnswer<User>() { private int mTimes = 0; @Override public User answer() throws Throwable { mTimes++; if( mTimes == 1 ){ throw new RuntimeException(); } return mockUser; } }).times(2); PowerMock.replayAll(); RetryExample retryExample = new RetryExample(); User user = retryExample.readUser(); assertEquals(mockUser, user); PowerMock.verifyAll(); }
IAnswer其實還能取得輸入參數,好去回傳不同的結果,這部分等之後有機會再分享給大家。
留言
張貼留言