在上一篇文章中,咱們講了要將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
,那麼相對於Retrofit
,UserManager
又是Dependency。DI的基本思想就是,對於Dependency的建立過程,並不在Client裏面進行,而是由外部建立好,而後經過某種方式set到Client裏面。這種模式,就叫作依賴注入。android
是的,依賴注入就是這麼簡單的一個概念,這邊須要澄清的一點是,這個概念自己跟dagger2啊,RoboGuice這些框架並無什麼關係。如今不少介紹DI的文章每每跟dagger2是在一塊兒的,由於dagger2的使用相對來講不是很直觀,因此致使不少人認爲DI是多麼複雜的東西,甚至認爲只能用dagger等框架來實現依賴注入,其實不是這樣的。實現依賴注入很簡單,dagger這些框架只是讓這種實現變得更加簡單,簡潔,優雅而已。git
下面介紹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
若是Constructor裏面傳入的不少是基本類型的數據或數據類,那麼或許你要作的,是建立一個(或者是另外一個)數據類把這些數據封裝一下,這個過程的價值但是大大滴!而不只僅是封裝一下參數的問題,有了一個類,不少的方法就能夠放到這個類裏面了。這點請參考Martin Fowler的《重構》第十章「Introduce Parameter Object」。this
若是傳入的不少是service類,那麼這說明這個類作的事情太多了,不符合單一職責的原則(Single Responsibility Principle,SRP),所以,須要重構。
接下來講回咱們的初衷:DI在測試裏面的應用。
所謂DI在單元測試裏面的應用,其實說白了就是使用DI模式,將mock出來的Dependency set到Client裏面去。我相信這篇文章解釋到這裏,那麼答案也就比較明顯了,爲了強調咱們要儘可能使用 Constructor injection,對於 setter Injection 和 Argument 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()
方法調用了mUserManager
的performLigon()
。對應的測試方法以下:
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的使用,目的是要強調:
一個靈活的,易於測試的,符合SRP的,結構清晰的項目,關鍵在於要應用依賴注入這種模式,而不是用什麼來作依賴注入。
等你學會使用dagger之後,要記得在測試的時候,若是能夠直接mock dependency並傳給被測類,那就直接建立,不是必定要使用dagger來作DI
然而若是徹底不使用框架來作DI,那麼在正式代碼裏面就有一個問題了,那就是dependency的建立工做就交給上層client去處理了,這可不是件好事情。想一想看,LoginActivity
裏面建立LoginPresenter
的時候,還得知道LoginPresenter
用了UserManager
。而後建立一個UserManager
對象給LoginPresenter
。對於LoginActivity
來講,它以爲我才懶得管你用什麼樣的UserManager
呢,我只想告訴你login的時候,你給我老老實實的login就行了,你用什麼Manager我無論。因此,直接在LoginActivity
裏面建立UserManager
,可能不是個好的選擇。那怎麼樣算是一個好的選擇呢?dagger2給了咱們答案。
因而下一篇文章咱們介紹dagger2。
文中的代碼在github這個項目裏面。
最後,若是你也對安卓單元測試感興趣的話,歡迎加入咱們的交流羣: