1. 程式人生 > >JUnit和mockito

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方法設定返回值