JUnit和mockito
我們都希望寫出沒bug的程式碼,那麼測試就是必不可少的一個環節。在CI持續整合併發布我們的程式碼的過程中,有很多測試方法,可以提高我們程式碼的覆蓋率,查缺補漏。
單元測試
單元測試用於測試最小的功能單元,這是各種測試中範圍最小的一種。在單元測試中,我們一般會測試一個方法以其為單位,檢測是否能返回我們期望的結果。理論上,單元測試應該在記憶體中進行,被測試的程式碼在理論上不應該和其他外部資訊進行互動,這些外部資訊包括:訪問網路、訪問資料庫、讀寫資料、訪問其他執行緒。
JUnit
除了讓整個應用執行起來之外,我們希望有一種比較方便的方式來測試我們的功能,JUnit就是這樣的一種框架,設想如果沒有單元測試框架,我們用main函式來測試過程實在是複雜而且難以想象。一般的,專案工程都會有一個test資料夾,我們將所有的測試方法都寫在這個資料夾下,測試方法的名字是無關的,但是我們一般最好起一個易懂的名字。
使用方法
-
@Test註解:表示當前方法為測試方法,我們可以執行該方法
-
@Before註解:表示當前類中,所有的方法在執行前都會執行被Before註解修飾的方法
-
@After註解:類似於Before,在每個方法執行完後都會執行此方法
-
Assert:測試方法一般返回值都是void,我們用Assert.assertTrue()等方法做斷言
-
@Ignore註解:如果測試方法還沒完成時,可以用這個註解,讓整合的時候忽略此測試方法
-
@Test(expected = Exception.class) : 如果測試後沒有丟擲期望的異常,則測試失敗
mockito
在測試的時候,有時我們要達到一些覆蓋率指標,或者即使不是因為覆蓋率指標我們也會遇到這種問題:測試方法A中呼叫了其他方法B,而B方法因某些原因不能成功呼叫或我們不想呼叫B方法。我們需要一套機制,讓測試增加可控,我們能比較自由的測試想要測試的部分,mockito就是幫助我們完成這件工作的工具。
mockito的主要作用有兩個:
-
驗證某例項的某方法在測試中是否被呼叫、被呼叫了多少次
-
跳過某例項的某方法的實際執行內容,直接返回我們設定的結果
mockito是單元測試的利器,我們開始說過,單元測試是專注於方法內部,最小單元的測試,在其中我們或許會難免涉及其他服務的方法呼叫等,我們可以利用mockito來跳過這些步驟。mock這個單詞的意義就是虛假、愚弄,我們用一個自己建立的假物件替換真實的物件達到我們的效果,並不會對原類、原物件產生改變性的影響。
mock
@Test
public void testLogin() throws Exception {
UserManager mockUserManager = Mockito.mock(UserManager.class);
//用上面的方法,我們建立了一個mock的UserManager物件
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.setUserManager(mockUserManager);
//我們將這個物件注入loginPresenter中,再後面我們使用註解時,有更便捷的方法
loginPresenter.login("xiaochuang", "xiaochuang password");
//在測試方法中呼叫一次我們要測試的方法
Assert.assertTrue(Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("xiaochuang", "xiaochuang password"));
//如果在第11行呼叫loginPresenter.login的方法內部,呼叫了performLogin方法1次,則這個斷言為true
//Mockito.verify(mockUserManager, Mockito.times(1)) 等價於 Mockito.verify(mockUserManager) 一次可以省略
Mockito.when(mockUserManager.verifyPassword("xiaochuang_is_handsome")).thenReturn(true);
//當呼叫mockUserManager的verifyPassword方法時,直接返回true
}
上面的程式碼與註釋基本解釋了mockito的工作原理,值得注意的是:mock生成的物件的所有的方法都不會真正執行,int、long型別方法將返回0,boolean方法將返回false,物件方法將返回null。只要用mock生成(被打上@mock註解)這個物件的所有方法都空了。而when方法只是為其中的某個方法指定一個返回結果。
spy
有的時候我們不希望所有方法都空,而是希望自己指定,那麼可以使用spy方法:
UserManager mockUserManager = Mockito.spy(UserManager.class);
//所有的方法都正常執行,只有被when呼叫的方法會直接返回
@Mock與@InjectMocks
現在我們整理一下使用mockito的思路:
-
我們建立一個mock的物件a,這個物件方法都空
-
在測試方法中,我們要測試的方法是物件b的方法C,也就是b.C
-
b物件中組合了物件a,我們需要把mock的物件a傳到b中替換掉真正的a,讓我們在b.C執行的內部,在遇到a的方法a.D、a.E執行時變成空
上述的三個步驟涉及一個繫結的過程,我們需要把a和b繫結在一起,在不使用註解的時候我們用setter解決這個問題,在有了註解之後,我們可以方便的使用註解。
@InjectMocks
@Resource(name = "loginPresenter")
LoginPresenter loginPresenter;
@Mock
UserManager mockUserManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
//這一步的作用是將Mocks註解和InjectMocks註解的物件自動裝配
}
@Test
public void testLogin() throws Exception {
loginPresenter.login("xiaochuang", "xiaochuang password");
//在測試方法中呼叫一次我們要測試的方法
Assert.assertTrue();
}
上述程式碼和我們開始的程式碼作用完全相同,但是因為使用了註解,就清晰了很多,其中我們initMocks方法進行了兩個註解的裝配。使用註解後,我們有兩點需要注意:
-
如果我們需要裝配的物件有三層,那麼肯定有一次層需要手動配置而不能使用兩個@InjectMocks註解
-
一般我們的方法都會返回物件,在@Mock後,返回物件的方法都返回null,如果有需要,我們要自己用when方法設定返回值