天天看點

App開發:模拟伺服器資料接口 - MockApi

為了友善app開發過程中,不受伺服器接口的限制,便于用戶端功能的快速測試,可以在用戶端實作一個模拟伺服器資料接口的MockApi子產品。本篇文章就嘗試為使用gradle的android項目設計實作MockApi。

在app開發過程中,在和伺服器人員協作時,一般會第一時間<code>确定資料接口的請求參數和傳回資料格式</code>,然後伺服器人員會盡快提供給用戶端可調試的假資料接口。不過有時候就算是假資料接口也來不及提供,或者是接口資料格式來回變動——很可能是用戶端展示的原因,這個是産品設計決定的,總之帶來的問題就算伺服器端的開發進度會影響用戶端。

是以,如果可以在用戶端的正常項目代碼中,自然地(不影響最終apk)添加一種模拟伺服器資料傳回的功能,這樣就可以很友善的在不依賴伺服器的情況下展開用戶端的開發。而且考慮一種情況,為了測試不同網絡速度,網絡異常以及伺服器錯誤等各種“可能的真實資料請求的場景”對用戶端UI互動的影響,我們往往需要做很多手動測試——千篇一律!如果本地有一種控制這種伺服器響應行為的能力那真是太好了。

本文将介紹一種為用戶端項目增加<code>模拟資料接口</code>功能的方式,希望能減少一些開發中的煩惱。

下面從<code>分層設計、可開關模拟子產品、不同網絡請求結果的制造</code>這幾個方面來闡述下模拟接口子產品的設計。

為了表達友善,這裡要實作的功能表示為“資料接口模拟子產品”,對應英文為MockDataApi,或簡寫為MockApi,正常的資料接口子產品定義為DataApi。

說到分層設計,MVC、MVP等模式一定程度上就起到了對代碼所屬功能的一個劃分。分層設計簡單的目标就是讓項目代碼更加清晰,各層互相獨立,好處不多說。

移動app的邏輯主要就是互動邏輯,然後需要和伺服器溝通資料。是以最簡單的情形下可以将一個功能(比如一個清單界面)的實作分UI層和資料通路層。

下面将資料通路層表述為DataApi子產品,DataApi層會定義一系列的接口來描述不同類别的資料通路請求。UI層使用這些接口來擷取資料,而具體的資料通路實作類就可以在不修改UI層代碼的情況下進行替換。

例如,有一個ITaskApi定義了方法<code>List&lt;Task&gt; getTasks()</code>,UI層一個界面展示任務清單,那麼它使用ITaskApi來擷取資料,而具體ITaskApi的實作類可以由DataApi層的一個工廠類DataApiManager來統一提供。

有了上面的分層設計,就可以為UI層動态提供真實資料接口或模拟資料接口。

可能大家都經曆過在UI層代碼裡臨時寫一些假資料得情況。比如任務清單界面,開發初,可以寫一個mockTaskData()方法來傳回一個<code>List&lt;Task&gt;</code>。但這種代碼隻能是開發階段有,最終apk不應該存在。

不能讓“模拟資料”的代碼到處散亂,在分層設計的方式下,可以将真實的資料接口DataApi和模拟資料接口MockDataApi分别作為兩個資料接口的實作子產品,這樣就可以根據項目的建構類型來動态提供不同的資料接口實作。

實作MockDataApi的動态提供的方法也不止一種。

一般的java項目可以使用“工廠模式+反射”來動态提供不同的接口實作類,再專業點就是依賴注入——DI架構的使用了。

目前gradle是java的最先進的建構工具,它支援根據buildType來分别指定不同的代碼資源,或不同的依賴。

可以在一個單獨的類庫module(就是maven中的項目)中來編寫各種MockDataApi的實作類,然後主app module在debug建構時添加對它的依賴,此時資料接口的提供者DataApiManager可以向UI層傳回這些mock類型的執行個體。

為了讓“正常邏輯代碼”和mock相關代碼的關聯盡量少,可以提供一個MockApiManager來唯一擷取各個MockDataApi的執行個體。然後在debug建構下的MockApiManager會傳回提供了mock實作的資料接口執行個體,而release建構時MockApiManager會一律返null。

MockApi在多次請求時提供不同的網絡請求結果,如伺服器錯誤,網絡錯誤,成功等,并模拟出一定的網絡延遲,這樣就很好的滿足了UI層代碼的各種測試需求。

為了達到上述目标,定義一個接口IMockApiStrategy來表示對資料請求的響應政策,它定義了方法onResponse(int callCount)。根據目前請求的次數callCount,onResponse()會得到不同的模拟響應結果。很明顯,可以根據測試需要提供不同的請求響應政策,比如不斷傳回成功請求,或者不斷傳回錯誤請求,或輪流傳回成功和錯誤等。

下面就給出各個部分的關鍵代碼,來說明以上所描述的MockDataApi子產品的實作。

作為示例,界面MainActivity是一個“任務清單”的展示。任務由Task類表示:

界面MainActivity使用一個TextView來顯示“加載中、任務清單、網絡錯誤”等效果,并提供一個Button來點選重新整理資料。代碼如下:

在UI層代碼中,使用DataApiManager.ofTask()獲得資料通路接口的執行個體。

考慮到資料請求會是耗時的異步操作,這裡每個資料接口方法接收一個<code>DataApiCallback&lt;T&gt;</code> 回調對象,T是将傳回的資料類型。

接口DataApiCallback定義了資料接口請求資料開始和結束時的通知。

根據分層設計,UI層和資料通路層之間的通信就是基于DataApi接口的,每個DataApi接口提供一組相關資料的擷取方法。擷取Task資料的接口就是ITaskApi:

UI層通過DataApiManager來獲得各個DataApi接口的執行個體。也就是在這裡,會根據目前項目建構是debug還是release來選擇性提供MockApi或最終的DataApi。

當MOCK_ENABLE為true時,會去MockApiManager檢索一個所需接口的mock執行個體,如果沒找到,會傳回真實的資料接口的實作,上面的NetTaskApi就是。倘若現在伺服器還無法進行聯合調試,它的實作就簡單的傳回一個伺服器錯誤:

DataApiManager利用MockApiManager來擷取資料接口的mock執行個體。這樣的好處是模拟資料接口的相關類型都被“封閉”起來,僅通過一個唯一類型來擷取已知的DataApi的一種(這裡就指mock)執行個體。這樣為分離出mock相關代碼打下了基礎。

在DataApiManager中,擷取資料接口執行個體時會根據開關變量MOCK_ENABLE判斷是否可以傳回mock執行個體。僅從功能上看是滿足動态提供MockApi的要求了。不過,為了讓最終release建構的apk中不包含多餘的mock相關的代碼,可以利用gradle提供的buildVariant。

<code>buildVariant</code>

使用gradle來建構項目時,可以指定不同的buildType,預設會有debug和release兩個“建構類型”。此外,還可以提供productFlavors來提供不同的“産品類型”,如demo版,專業版等。

每一種productFlavor和一個buildType組成一個buildVariant(建構變種)。

可以為每一個buildType,buildVariant,或productFlavor指定特定的代碼資源。

這裡利用buildType來為debug和release建構分别指定不同的MockApiManager類的實作。

預設的項目代碼是在src/main/java/目錄下,建立目錄/src/debug/java/來放置隻在debug建構時編譯的代碼。在/src/release/java/目錄下放置隻在release建構時編譯的代碼。

debug建構時的MockApiManager

靜态方法getMockApi()根據傳遞的接口類型資訊從mockApis中擷取可能的mock執行個體,mockApis中注冊了需要mock的那些接口的實作類對象。

release建構時的MockApiManager

因為最終release建構時是不需要任何mock接口的,是以此時getMockApi()一律傳回null。也沒有任何和提供mock接口相關的類型。

通過為debug和release建構提供不同的MockApiManager代碼,就徹底實作了MockApi代碼的動态添加和移除。

模拟資料接口的思路非常簡單:根據請求的次數callCount,運作一定的政策來不斷地傳回不同的響應結果。

響應結果包括<code>“網絡錯誤、伺服器錯誤、成功”</code>三種狀态,而且還提供一定的網絡時間延遲的模拟。

接口IMockApiStrategy的作用就是抽象對請求傳回不同響應結果的政策,響應結果由IMockApiStrategy.Response表示。

Response表示的響應結果包含結果狀态和延遲時間。

作為一個預設的實作,WheelApiStrategy類根據請求次數,不斷傳回上述的三種結果:

方法onResponse()的參數out僅僅是為了避免多次建立小對象,對應debug建構,倒也沒太大意義。

針對每一個資料通路接口,都可以提供一個mock實作。比如為接口ITaskApi提供MockTaskApi實作類。

為了簡化代碼,抽象基類BaseMockApi完成了大部分公共的邏輯。

<code>onResponse()</code>

方法onResponse()根據“響應政策”來針對一次請求傳回一個“響應結果”,預設的政策由方法getMockApiStrategy()提供,子類可以重寫它提供其它政策。當然政策對象本身也可以作為參數傳遞(此時此方法本身也沒多大意義了)。

一個想法是,每一個MockApi類都隻需要一個執行個體,這樣它的callCount就可以在程式運作期間得到保持。此外,大多數情況下政策對象隻需要一個就行了——它是無狀态的,封裝算法的一個“函數對象”,為了多态,沒辦法讓它是靜态方法。

<code>giveErrorResult()</code>

此方法用來執行錯誤回調,此時是不需要資料的,隻需要根據response來執行一定的延遲,然後傳回網絡錯誤或伺服器錯誤。

注意一定要在main線程上執行callback的各個方法,這裡算是一個約定,友善UI層直接操作一些View對象。

<code>giveSuccessResult()</code>

此方法用來執行成功回調,此時需要提供資料,并執行response中的delayMillis延遲。

參數dataMethod用來提供需要的假資料,這裡保證它的執行在非main線程中。

同樣,callback的方法都在main線程中執行。

上面BaseMockApi中的rxjava的一些代碼都非常簡單,完全可以使用Thread來實作。

作為示例,這裡為ITaskApi提供了一個mock實作類:

它的代碼幾乎不用過多解釋,使用代碼提供需要的傳回資料是非常簡單的——就像你直接在UI層的Activity中寫一個方法來造假資料那樣。

無論如何,經過上面的一系列的努力,模拟資料接口的代碼已經稍具子產品性質了,它可以被動态的開關,不影響最終的release建構,可以為需要測試的資料接口靈活的提供想要的mock實作。

很值得一提的是,整個MockApi子產品都是建立在純java代碼上的。這樣從UI層請求到資料通路方法的執行,都最終是直接的java方法的調用,這樣可以很容易擷取調用傳遞的“請求參數”,這些參數都是java類。而如果mock是建立在網絡架構之上的,那麼額外的http封包的解析是必不可少的。

僅僅是為了測試的目的,分層設計,讓資料通路層可以在真實接口和mock接口間切換,更簡單直接些。

最後,造假資料當然也可以是直接讀取json檔案這樣的方式來完成,如果伺服器開發人員有提供這樣的檔案的話。

以上所述代碼可以在這裡擷取到:

https://github.com/everhad/AndroidMockApi

如果你的項目裡有模拟伺服器接口這樣的需要,try it out!

(本文使用Atom編寫)