Android單元測試(五):依賴注入,將mock方便的用起來

上一篇文章中,咱們講了要將mock出來的dependency真正使用起來,須要在測試環境下經過某種方式set 到用到它的那個對象裏面進去,替換掉真實的實現。咱們前面舉的例子是:html

public class LoginPresenter {
    private UserManager mUserManager = new UserManager();

    public void login(String username, String password) {
        //。。。some other code
        mUserManager.performLogin(username, password);
    }
}

在測試LoginPresenter#login()時,爲了可以將mock出來的UserManager set到LoginPresenter裏面,咱們前面的作法是簡單粗暴,給LoginPresenter加一個UserManager的setter。然而這種作法畢竟不是很優雅,通常來講,咱們正式代碼裏面是不會去調用這個setter,修改UserManager這個對象的。所以這個setter存在的意義就純粹是爲了方便測試。這個雖然不是沒有必要,卻不是太好看,所以在有選擇的狀況下,咱們不這麼作。在這裏,咱們介紹依賴注入這種模式。java

對於依賴注入(Dependency Injection,如下簡稱DI)的準肯定義能夠在這裏找到。它的基本理念這邊簡單描述下,首先這是一種代碼模式,這個模式裏面有兩個概念:Client和Dependency。假如你的代碼裏面,一個類用到了另一個類,那麼前者叫Client,後者叫Dependency。結合上面的例子,LoginPresenter用到了UserManager,那麼LoginPresenter叫Client,UserManager叫Dependency。固然,這是個相對的概念,一個類能夠是某個類的Dependency,倒是另一個類的Client。好比說若是UserManager裏面用到了Retrofit,那麼相對於RetrofitUserManager又是Dependency。DI的基本思想就是,對於Dependency的建立過程,並不在Client裏面進行,而是由外部建立好,而後經過某種方式set到Client裏面。這種模式,就叫作依賴注入。android

是的,依賴注入就是這麼簡單的一個概念,這邊須要澄清的一點是,這個概念自己跟dagger2啊,RoboGuice這些框架並無什麼關係。如今不少介紹DI的文章每每跟dagger2是在一塊兒的,由於dagger2的使用相對來講不是很直觀,因此致使不少人認爲DI是多麼複雜的東西,甚至認爲只能用dagger等框架來實現依賴注入,其實不是這樣的。實現依賴注入很簡單,dagger這些框架只是讓這種實現變得更加簡單,簡潔,優雅而已。git

DI的常見實現方式

下面介紹DI的實現方式,一般來講,這裏是大力介紹dagger2的地方。可是,雖然dagger2的確是很是好的東西,然而若是我直接介紹dagger2的話,會很容易致使一個誤區,認爲在測試的時候,也只能用dagger來作依賴注入或建立對應的測試類,所以,我這邊刻意不介紹dagger。先讓你們知道最基本的DI怎麼實現,而後在測試的時候如何更方便高效的使用。github

實現DI這種模式其實很簡單,有多種方式,上一篇文章中提到的setter,其實就是實現DI的一種方式,叫作 setter injection 。此外,經過方法的參數傳遞進去(argument injection),也是實現DI的一種方式:框架

public class LoginPresenter {

    //這裏,LoginPresenter再也不持有UserManager的一個引用,而是做爲方法參數直接傳進去
    public void login(UserManager userManager, String username, String password) {
        //... some other code
        userManager.performLogin(username, password);
    }
}

然而更經常使用的方式,是將Dependency做爲Client的構造方法的參數傳遞進去:單元測試

public class LoginPresenter {

    private final UserManager mUserManager;

    //將UserManager做爲構造方法參數傳進來
    public LoginPresenter(UserManager userManager) {
        this.mUserManager = userManager;
    }

    public void login(String username, String password) {
        //... some other code
        mUserManager.performLogin(username, password);
    }
}

這種實現DI的模式叫 Constructor Injection。其實通常來講,提到DI,指的都是這種方式。這種方式的好處是,依賴關係很是明顯。你必須在建立這個類的時候,就提供必要的dependency。這從某種程度上來講,也是在說明這個類所完成的功能。所以,儘可能使用 Constructor injection測試

說到這裏,你可能會有一個疑問,若是把依賴都聲明在Constructor的參數裏面,這會不會讓這個類的Constructor參數變得很是多?若是真的發生這種狀況了,那每每說明這個類的設計是有問題的,須要重構。爲何呢?咱們代碼裏面的類,通常能夠分爲兩種,一種是Data類,好比說UserInfo,OrderInfo等等。另一種是Service類,好比UserManager, AudioPlayer等等。因此這個問題就有兩種狀況了:ui

  1. 若是Constructor裏面傳入的不少是基本類型的數據或數據類,那麼或許你要作的,是建立一個(或者是另外一個)數據類把這些數據封裝一下,這個過程的價值但是大大滴!而不只僅是封裝一下參數的問題,有了一個類,不少的方法就能夠放到這個類裏面了。這點請參考Martin Fowler的《重構》第十章「Introduce Parameter Object」。this

  2. 若是傳入的不少是service類,那麼這說明這個類作的事情太多了,不符合單一職責的原則(Single Responsibility Principle,SRP),所以,須要重構。

接下來講回咱們的初衷:DI在測試裏面的應用。

DI在單元測試裏面的應用

所謂DI在單元測試裏面的應用,其實說白了就是使用DI模式,將mock出來的Dependency set到Client裏面去。我相信這篇文章解釋到這裏,那麼答案也就比較明顯了,爲了強調咱們要儘可能使用 Constructor injection,對於 setter InjectionArgument injection 這邊就不作代碼示例了。
若是你的代碼使用的是 Constructor injection

public class LoginPresenter {

    private final UserManager mUserManager;

    //將UserManager做爲構造方法參數傳進來
    public LoginPresenter(UserManager userManager) {
        this.mUserManager = userManager;
    }

    public void login(String username, String password) {
        //... some other code
        mUserManager.performLogin(username, password);
    }
}

其中咱們要測的方法是login(), 要驗證login()方法調用了mUserManagerperformLigon()。對應的測試方法以下:

public class LoginPresenterTest {

    @Test
    public void testLogin() {
        UserManager mockUserManager = Mockito.mock(UserManager.class);
        LoginPresenter presenter = new LoginPresenter(mockUserManager);  //建立的時候,講mock傳進去

        presenter.login("xiaochuang", "xiaochuang password");

        Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");
    }
}

很簡單,對吧。

小結

這篇文章介紹了DI的概念,以及在單元測試裏面的應用,這裏特地沒有介紹dagger2的使用,目的是要強調:

  1. 一個靈活的,易於測試的,符合SRP的,結構清晰的項目,關鍵在於要應用依賴注入這種模式,而不是用什麼來作依賴注入。

  2. 等你學會使用dagger之後,要記得在測試的時候,若是能夠直接mock dependency並傳給被測類,那就直接建立,不是必定要使用dagger來作DI

然而若是徹底不使用框架來作DI,那麼在正式代碼裏面就有一個問題了,那就是dependency的建立工做就交給上層client去處理了,這可不是件好事情。想一想看,LoginActivity裏面建立LoginPresenter的時候,還得知道LoginPresenter用了UserManager。而後建立一個UserManager對象給LoginPresenter。對於LoginActivity來講,它以爲我才懶得管你用什麼樣的UserManager呢,我只想告訴你login的時候,你給我老老實實的login就行了,你用什麼Manager我無論。因此,直接在LoginActivity裏面建立UserManager,可能不是個好的選擇。那怎麼樣算是一個好的選擇呢?dagger2給了咱們答案。
因而下一篇文章咱們介紹dagger2。

文中的代碼在github這個項目裏面

最後,若是你也對安卓單元測試感興趣的話,歡迎加入咱們的交流羣:

做者 小創 更多文章 | Github | 公衆號

相關文章
相關標籤/搜索