一個Android項目搞定所有主流架構:MVP+單元測試

相關閱讀:

[乾貨]2017已來,最全面試總結——這些Android面試題你一定需要

吊炸天!74款APP完整源碼!

Java面試知識點總結


公衆號:Java和Android架構

關注回覆:Android,iOS,PHP,js,HTML5,Python,機器學習 ,AI,大數據Hadoop,c++,J2EE等關鍵字就能免費獲取學習資料視頻


開始開發安卓時應該都是使用最基本的MVC結構,雖然簡單但是不利於測試、維護和拓展。


而本項目就是爲了解決這個問題誕生的,在這個項目中我們提供了相同的應用程序使用不同的框架實現之。


包括MVC、MVP、MVP+Dagger2等,本篇介紹的就是現在最主流的MVP結構以及展示瞭如何編寫單元測試。


Github地址如下,其中有詳細完全的介紹文檔:


https://github.com/boredream/DesignResCollection



已開發完成的示例



  • DesignResCollection_MVC/ - Model-View-Controller 結構。

  • DesignResCollection_MVP/ - Model-View-Presenter 結構。


其中包含了針對P層的junit邏輯測試代碼以及針對V層的Espresso頁面測試代碼。



爲什麼選擇MVP?



相信大部分人都聽過這個框架,或者已經使用過。

瞭解和簡單運用的過程中大家一定會有這樣幾個問題或者痛點:


  • MVP有什麼好處,爲什麼要用它?

  • MVP結構代碼怎麼寫?

  • 爲什麼MVP結構利於單元測試?而且我爲什麼要寫測試代碼呢?

  • 好了你說服我了,但是我不會寫單元測試啊!

  • MVP多了好多類,還要寫測試代碼,寫起來好累啊!老孃不想這麼麻煩啊!


這裏班門弄斧的分享下我的經驗,挨個解決這幾個問題。



MVP有什麼好處,爲什麼要用它?



網上文章一大堆,總結下來主要有下面幾個優點:


  • 代碼解耦、結構更清晰

  • 更好的拓展性

  • 可複用性

  • 利於單元測試


優點其實主要是相對傳統MVC結構而言的,簡單對比下:


  • MVC(Model-View-Controller)

  傳統MVC結構中,C承擔着一個總控制器的作用,處理Model數據,再控制View的顯示。

大部分時候Activity類就是這個角色,我們在Activity中調用接口,接口返回數據後各種setText setImage顯示到UI上。


  • MVP(Model-View-Presenter)

  重點在於Presenter,它其實是將Model和View分開了,在其中起到一箇中轉站的角色。

把Model數據拿來一通處理,然後丟給View讓它自己去解決具體的UI顯示。


打個比方: 如果處理Model處理業務邏輯就是加工食材做菜。把菜送到客戶手裏呈現給客戶就是View的展示。

那MVC就是大排檔。C就是獨自運營的老闆,自己炒菜,做完再自己送到小桌子上的客戶面前,一條龍。

MVP就是正規大餐廳,P則是後廚中心,海綿寶寶做好蟹黃堡後放到窗口處,叮一下通知前臺好了可以送餐了,不用關心菜是怎麼送到客戶手裏的。然後由服務員章魚哥在窗口處取了餐,再或跑或跳或踩着軲轆鞋最後送到客戶手裏,合作完成。


所以這裏也可以看出來,MVP最重要的特點就是:


將 Model業務邏輯處理 和 View頁面處理 分開!!!


MVP的良好拓展性、解耦、利於單元測試等優點基本都是來源於此。

純語言描述大家可能還是不好理解,下面上實戰項目。



MVP結構代碼怎麼寫?



示例項目中的MVP結構參考了谷歌官方MVP示例項目中的寫法。每個功能模塊都包含以下幾部分:




Contract協議類



      這個Contract協議類不是MVP中的任何一個模塊,是把所有View和Presenter的方法都提取成了接口放在這裏,作爲一個總的規則、協議,方便統一管理。

比如下面的代碼,就是示例項目中意見反饋頁面的Contract協議類,提供了View和Presenter的接口。

其中BaseView和BasePresenter是提供了一些基礎方法,比如顯示進度showProgress等,自己可以按需添加。





Model



數據層,和MVC結構中的無區別,沒啥好說的。



Presenter



負責處理業務邏輯代碼,處理Model數據,然後分發給View層的抽象接口。

  注意:這裏是將處理好的數據派發給View的抽象接口,是一個簡單的中轉分發出去,並不負責具體展示。




View



負責UI具體實現展現。比如Presenter派發過來一個動作是showProgress顯示進度命令,那由我這個View負責實現具體UI,是顯示進度框還是顯示一個下拉刷新圈圈等,都是View這裏自行控制。

  Google的例子中,每個Activity中都會添加一個Fragment作爲View實現,Activity僅僅作爲一個容器,包含一個Fragment在其中顯示各種控件。   我覺得其實也可以直接將Activity作爲View。本示例代碼中兩種方式都有,可以根據需要自行選擇方式~





注意:這裏BaseView中會有一個isActivite方法,用於判斷視圖是否被銷燬。我在BaseActivity中會統一處理,添加一個isActivite變量,onStart時設爲true,onStop時設爲false。


然後在presenter裏的接口返回數據後,判斷view是否被銷燬然後再控制顯示,因爲接口是異步的,所以返回數據後視圖可能已經銷燬,那就沒必要更新了,更新反而還會崩潰報錯。


好了,現在再回頭看看MVP的幾個優點,可能就有更好的理解了(當然,還是要自己擼過一遍最好)。



更好的拓展性



  • 某天頁面需要加功能了,協議類中先寫好對應的P邏輯方法、V頁面方法,然後在實現類中分別編寫具體代碼即可。

  • 某天突然改功能了,說所有錯誤提示我們不用Toast,用Dialog吧,那直接在showTip處修改即可。

  • 某天產品突然告訴你說意見反饋,失敗我們也讓用戶覺得成功,那直接在Error回調裏調用view抽象方法即可。



解耦、更好的代碼結構



  • 業務邏輯 和 頁面UI 代碼分開,不揉在一起,改邏輯的時候不用關心UI,反之亦然。

  • 想了解某個模塊功能時,直接在協議類中看一個個抽象方法,不用關心代碼,清晰明瞭。

  • 還有代碼可以分工合作,核心業務邏輯你在P中自己寫,UI的具體實現直接給其他人合作寫。



可複用性



比如本項目中的註冊功能,註冊步驟1和步驟2頁面中都有發送驗證碼功能,那就可以使用同一個P了,在其中調用獲取驗證碼接口。然後 各自實現具體View顯示,步驟1頁面獲取驗證碼成功後跳轉到頁面2,頁面2獲取成功後開始數字倒計時。


爲什麼MVP結構利於單元測試?


之前提到過,MVP結構最大的特點是,P將邏輯和UI分開了。即P 中沒有任何Android相關的代碼,比如Toast啊、setText等等。這意味着~ 你可以針對Presenter寫junit測試了。只對java代碼的測試,不用涉及任何UI!不用運行模擬器的測試!速度起飛的測試!


說的這麼熱鬧,那麼



我爲什麼要寫測試代碼呢?不是浪費時間嗎?



測試其實除了檢測bug驗證邏輯之外,還有最重要的一個功能是提高開發速度!

你沒有看錯,雖然寫了更多的代碼,但實際效率是提升的,尤其對越龐大越複雜的應用來說。


可能我這樣說不夠權威,可以看下經典書籍《重構》然後自己嘗試一下,可能就會有感受了。



怎麼寫測試代碼呢?



我們先介紹下Android中的兩種測試



UI測試(本項目中使用框架Espresso)



UI測試其實就是模擬機器上的操作行爲,讓它自動進行的“點擊某個位置”、“輸入某些字符串”等行爲。

  是依賴安卓設備的,測試的時候可以在手機或模擬器屏幕上看到頁面被各種點點點,輸輸輸,跳來跳去。


  這個其實和MVP結構關係不大,MVC,MVP,或者MVABCDEFG都可以進行UI測試,所以這裏暫時不多做介紹,可以直接參考示例項目中的代   碼。UI測試部分的內容其實也很多,以後單獨拿出來再詳細展開。


  項目中androidTest文件夾裏的就是UI測試代碼,而test文件夾纔是Junit部分的單元測試代碼。



對Presenter進行Junit單元測試


(本項目中使用框架Mockito)


UI測試雖然接近真實場景,但是有個缺點是要運行應用到模擬器上,所以速度就會有影響,慢~

  而且開發中也會常有這樣一個需要,調試接口時,我不想點點點跳轉到那個頁面再輸入東西再點按鈕,費時間啊~而用postman啥的工具也麻煩,header還要重新寫,如果有參數加密就更麻煩了。


  所以,這個時候你就需要Junit單元測試了,最大的特點就是不用運行安卓設備,直接run代碼,速度飛快!



單元測試代碼示例



正式開始介紹怎麼寫之前,先感受下單元測試是什麼樣的,如下圖




這裏針對示例項目中意見反饋Presenter分別測試了幾個場景


  • 真實接口提交成功

  • 模擬接口提交成功

  • 模擬接口提交失敗


三個Test方法,針對三個測試場景。

突破左下角運行情況可以看到,一共用了852ms,1秒不到!!!

第一個測試方法因爲是真實調用接口數據,所以稍微耗費點時間。

右下角也可以看到3個用例全測試成功通過,也打印了真實調用數據的接口日誌。

完美~




如何寫單元測試代碼



編寫步驟按照以下進行



1. 新建Presenter的測試類



右鍵Presenter類 -> Go To -> Test -> create new test




彈出一個創建測試類對話框,然後勾選需要測試的方法(當然也可以自己手動創建方法)。


然後OK,選擇test文件夾完成測試類創建。





2. 測試類的初始化



代碼如下(mockito的gradle配置等參考項目中build.gradle)




這裏用到了一個很重要的框架 Mockito。



Mockito框架介紹



mockito框架是用來模擬數據和情景的,方便我們的測試工作進行。



爲什麼要用Mockito框架?



比如我們MVP結構中P的測試,有個問題是:創建Presenter對象的時候這個View怎麼辦?傳入null會空指針啊。還有很多接口調用等邏輯,很多奇怪的失敗情況怎麼測試?


  這個時候就可以用mockito了~ 直接模擬一個view接口對象,不用關心它的具體實現;失敗情況直接用when方法搞定;此外還提供了其他一系   列方便測試的方法,比如verify用於判斷某對象是否執行了某個方法等。後面會根據例子挨個介紹。


  網上很多例子其實是純mock模擬測試,也就是接口api也是模擬的,模擬接口調用,模擬接口返回數據。

  雖然這樣速度快且方便模擬各種錯誤情況,但是有時候也會想要測試真實的接口返回情況,因此本項目示例中提供了兩種模擬和真實接口的寫   法和處理。參考上面代碼裏的presenter和mockPresenter對象。


  注意:mock相關方法比如verify、when等使用者也都必須是mock對象,所以使用presenter的時候不能用when什麼的方法模擬接口返回。


  @Before標籤的方法,是每個測試方法調用前都會走一遍的方法,因此在裏面放了一系列的初始化操作,每個操作都添加了註釋。其中需要單獨解釋的是when方法。


when(view.isActive()).thenReturn(true);


這個是mockito框架提供的一個方法,看英文基本就能瞭解什麼意思了,當xx方法調用時就返回xx,因爲我們的view的模擬的,所以沒有實現isActive方法,則p中數據返回後就無法繼續走下去了,因此這裏when處理一下。只要調用這個方法就返回true。



3. 測試方法編寫 



通常Presenter中的一個業務方法會對應至少一個測試方法。


比如這裏的意見反饋業務,就分別對應意見提交成功、失敗兩種情景。


方法名字可以隨便定,有個@Test標籤即可,推薦方法取名爲:test+待測方法原名+測試場景。


測試場景一共有哪些呢?這個最好問測試要個測試用例按照待測功能對應的所有情景挨個來。

我這裏寫的單元測試代碼,對於接口又分了兩種: 模擬接口 和 真實接口


直接全部用真實接口測不很好嗎,爲什麼要mock模擬測試呢?

好吧,比如我們這個意見反饋,不像登錄還有密碼錯誤的情況,很少有場景能失敗。怎麼辦?

所以對於難以模擬的情景,還是需要用mockito框架模擬的,模擬個失敗,然後驗證失敗後的一系列邏輯~

下面挨個介紹測試方法,模擬成功和失敗差不多就只介紹失敗了。



模擬接口測試方法示例 - 模擬提交失敗




這裏重點是when的運用,當模擬的api調用addFeedBack時,就返回error結果。

然後調用mockPresenter的意見反饋業務方法,最後驗證結果。

注意,這個verify方法也是特別常用的一個mockito方法,用於驗證某個對象是否執行了某個方法。

最後運行測試,成功,完美~

.


真實接口測試方法示例 - 提交成功





這裏用了真實接口對應的presenter對象,調用接口,然後驗證成功結果。

運行測試,成功,完美~


再次強調,mockito的方法都是針對模擬對象的,所以調用真實請求api時,你也想用when去處理,那就會報錯~


注意:真實接口由於是異步的,所以如果不做任何處理是無法測試通過的,接口數據還沒返回就運行下面的驗證了,自然失敗。因此需要對回調做一個處理,將其修改爲同步請求,這樣就能一條線下來了,運行完接口再進行驗證。項目是基於Retrofit框架的,使用RxJava處理回調,我這裏所有的回調都會用一個ObservableDecorator處理一下,而在其中我會判斷,如果當前是測試狀態(也就是Before中的那個isUnitTest 參數),就將回調設置爲同步,具體代碼參考項目中。



4. 運行單元測試用例



  • 右鍵方法,run 測試單個用例方法

  • 右鍵類,run 測試該類中包含的全部用例方法


最後控制檯看結果

參考最上面單元測試代碼示例中的截圖,下面控制檯會顯示測試了哪些方法,測試成功通過了幾個方法,然後打印相應日誌,如果不通過還會打印對應錯誤信息。


好了,寫法介紹完畢~

更多例子請去項目中查看,這裏篇幅有限就不太詳細的展開了,簡單列舉幾個例子讓大家感受下。



MVP多了好多類,還要寫測試代碼,好累啊!



這一點估計是最重要的原因把絕大部分人阻擋在門外。

畢竟平常普通的擼就那麼累了,還要這麼麻煩,沒時間啊沒精力啊!!!



不一定所有功能都用MVP



就像之前例子舉得那樣,大排檔和正規餐廳。你在一個超級偏遠沒人流量生意差到爆的地方還整個後廚中心,就過了。同理,如果你有的功能業務邏輯比較簡單,自然就沒必要MVP了,簡單的關於頁面你也一頓MVP可能就有點猛了,所以不一定所有功能都使用MVP。



單元測試利於開發



代碼結構啥的就不說了,單元測試這個有時候真的很方便,尤其是運行快。相信大部分人都有經驗,遇到個不靠譜後臺的時候,經常要陪他們調接口,再遇到那種特別深的頁面簡直是浪費人生。單元測試代碼,run,唰~秒搞定。自測某些邏輯功能時也很有用,這一點上看來絕對是節省時間的。



LiveTemplate


(乾貨!!!一鍵生成模板代碼,模板可自定義!)

我通常擼的時候特別特別注重速度效率。之前也開發過很多插件工作,比如已經發布的自動生成代碼佈局的開源AndroidStudio插件:https://github.com/boredream/BorePlugin


  然後就尋思,寫這種特別有規律的MVP各種類,還有測試類等的時候,要不也弄個插件生成下?

  但是想了下覺得插件生成模板代碼的話,模板怎麼寫呢?尤其MVP這種不同的人寫法也不同啊。

  最後突然想起來了AndroidStudio裏自帶的LiveTemplate這東西,是AS中自帶的一個模板代碼系統。




使用LiveTemplate模板



先展示下該功能的強大,這裏我以前提前寫好過幾個模板了。拿協議類舉例。


1.右鍵需要生成的位置 -> New -> 選擇模板(如下圖的MvpContract)



2.然後彈出對話框,爲模板輸入需要的變量,OK生成



這樣就按照我們的模板創建了一個文件,右側文件代碼全部都是自動生成的,然後按需修改加入方法即可。


那麼模板哪裏來的呢~下面介紹



編輯/創建LiveTemplate模板



1.編輯已有模板: New -> 選擇模板的時候,模板底部有個Edit File Template,點擊之。參見上面使用步驟1的圖。

2.創建新的模板:打開你希望生成模板的文件,選擇工具欄中的Tool -> Save File as Template。

3.步驟1、 2都會打開下面這樣一個編輯頁面,區別在於創建比編輯少個左側的已有模板列表。

給模板起個名字,然後在內容頁面里根據需要刪刪改改即可,模板裏所有${NAME}的地方都會替換成你創建模板時候輸入的文件名,其他的${XXX}的作用可參考下面Description裏的描述。最後OK保存模板。




LiveTemplate雖然無法替你搞定絕大部分代碼,但是這樣一個快捷的模板,可以靈活的隨時編輯還是很方便的,還是能節省相當代碼量的。

和本期主題無關的插個話,LiveTemplate是個很神奇的東西,很多地方都可以用,不光有文件的模板,代碼也是。比如輸入sout+回車就會自動生成System.out.print()代碼,輸入Toast+回車就會自動生成Toast.make blablabl的代碼,超級方便。比如你們項目有BaseActivity,需要複寫幾個方法,那就可以自定義創建個頁面類文件模板裏面處理好繼承和方法,就不用每次新建完Activity都去寫一下繼承了。更多用法期待你滴挖掘~



結語



好了,之前提的所有問題和痛點都挨個解答過了,尤其最後的LiveTemplate,對於還不知道的同學,即使最後你還是不願意用MVP和寫單元測試,那這部分你也算賺到了哈哈。

因爲要介紹的內容比較多,MVP啊~測試啊~Junit單元測試啊~LiveTemplate啊~ 所以介紹的比較精簡,主旨在拋磚引玉,希望大家對這幾個東西能有個瞭解,感興趣後再深入研究,也希望與我多多交流大家共同進步。


本項目裏Junit測試模塊其實還是有幾個問題的,比如Presenter我是將接口Api作爲構造函數參數依賴注入的,所以其實還可以再加入Dagger2改進一番,下一個框架就會在MVP的結構上加入Dagger2。


谷歌例子中RxJava是單獨拎出來說的,我這裏Retrofit2+RxJava是作爲所有例子通用框架的,用法可以給大家作爲一個參考,這裏就不掃盲Retrofit用法了。


當你看完之後,心中若有有疑問,不妨在下方留言與,支持作者的可以在他Github給個Star,支持小編的可以在下方+1。



關於Java和Android大牛頻道

Java和Android大牛頻道是一個數萬人關注的探討Java和Android開發的公衆號,分享和原創最有價值的乾貨文章,讓你成爲這方面的大牛!

我們探討android和Java開發最前沿的技術:android性能優化 ,插件化,跨平臺,動態化,加固和反破解等,也討論設計模式/軟件架構等。由羣來自BAT的工程師組成的團隊

關注即送紅包,回覆:“百度” 、“阿里”、“騰訊” 有驚喜!!!關注後可用入微信羣。羣裏都是來自百度阿里騰訊的大牛。

歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。搜索微信公衆號:JANiubility。

公衆號:JANiubility

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章