單元測試的作用
爲了使工作完成的更加輕鬆,設計更加的完善,減少調試的時間提高代碼的質量。
什麼是單元測試
單元測試是開發者編寫的一小段代碼,用於檢驗被測代碼的一個很小的、很明確的功能是否正確,通常而言,一個單元測試是用於判斷某個特定條件(或者場景)下某個特定函數的行爲。
Android 端的單元測試
什麼是mvp
關於單元測試
對於單元測試需要了解
- Android Studio的test和AndroidTest
- AndroidJUnitRunner:一個兼容Junit4的Andriod單元測試框架
- Mockito:單元測試利器
- Espresso:支持UI測試的單元測試框架
MVP各層的單元測試選型
在項目中,MVP各層所使用的單元測試框架如下圖所示:
- P層:不需要任何Android環境,因此使用Junit測試即可
- V層:使用Google強大的Espresso進行UI的測試
- M層:涉及到數據庫相關操作,因此需要依賴Android環境,使用AndroidJUnitRunner進行測試
各層的配合測試
推薦谷歌官方mvp實例項目 https://github.com/googlesamples/android-architecture
接下來我們以TO-DO List頁面(TasksActivity/TaskFragment)中加載任務列表功能爲例,此場景的功能界面如下圖所示:
P層測試
P層在這裏就只做了loadTask()事情
loadTask()時序圖
從圖中看loadTask()執行邏輯,
1.調用V層開啓加載框 -->2.調用M層獲取列表數據 --> 3.M層以回調模式返回數據 -->4.調用V層關閉加載框 --> 5.調用v層顯示列表數據
這5個步驟裏面,每個步驟的邏輯是否精準是V層和M層需要測試的,對於P層來說,它只需要確認這5個步驟如期調用就行.
這裏使用mockito,在測試類TasksPresenterTest中
總結:讓Presenter充當個合格的皮條客,去調用其他兩層的邏輯,在假設其他兩層代碼邏輯都是正確的前提下,做一些mock測試,儘可能覆蓋所有邏輯路徑。
V層的測試
對於V層在加載任務列表中的邏輯就只有顯示加載框-->隱藏加載框 →顯示列表數據
既然這麼簡單那麼我們驗證添加任務到顯示任務列表
流程:點擊添加按鈕-->打開軟鍵盤輸入標題和描述-->保存代辦任務-->返回任務列表--> 驗證輸入的代辦任務是否存在
通過Espresso可以模擬這些步驟,並進行驗證。這個測試類是TasksScreenTest
1.首先是添加任務和輸入標題描述,保存代辦任務
2.返回任務列表就需要刷新列表–其實也就是刷新數據–相當於點擊選擇all標籤
3.驗證剛剛的任務是否存在
整個V層測試方法:
總結:Espresso好強大,V層測試是站在用戶角度去測試每一個操作,它也不止測試了V層,還測試了一部分的P層和M層
M層測試
關於Model層的測試,首先要了解下該項目中,model層的設計,類層次如下圖所示:
-
TasksLocalDataSource:負責本地數據庫增刪改查操作
-
TasksRemoteDataSource:負責網絡請求(該項目中用handler.postDelayed()延時來模擬網絡請求)
-
TasksRepository:相當於整個Model層的門面,根據邏輯判斷決定數據來自於本地數據庫或是網絡。Presenter層只與它打交道。
根據以上分析,可見對Model層的測試要完整的覆蓋這三個類。
1.我們先看門面TasksRepository的測試,先看看這個類中有關獲取待辦任務列表的流程圖:
所以對於TasksRepository來講,測試的內容主要是驗證1,2,3的邏輯是否在相應的輸入下覆蓋到位,對於1,2,3的數據準確性無需關心,
由各自DataSource去驗證,因此它的測試與Android環境無關,用Junit+Mockito測試。要完整覆蓋的話,需要多個測試case,
篇幅有限,這裏只講第2種。這個測試類是TasksRepositoryTest,代碼如下:
2.接下來是是TasksLocalDataSource的測試。該測試與數據庫有關,因此依賴於Android環境,且要驗證數據存取的準確性,
因此需要做一些斷言,使用AndroidJUnitRunner進行測試,這個類是TasksLocalDataSourceTest
,代碼如下:
3.最後來看看跟網絡請求相關的TasksRemoteDataSource的測試
Google並沒有對這個類本身進行測試,但是對其他層依賴網絡請求數據進行測試的場景做了支持。試想一下,通過上面的分析,
我們知道View層是真刀真槍的在模擬用戶的操作進行測試,如果某個測試case需要發起網絡請求,此時我們不知道何時才能返回數據,
且由於網絡狀況等原因可能導致請求失敗,種種不確定因素下,是不可能完成一個測試的,解決的辦法很簡單,
就是對網絡請求進行Fake,這個類是FakeTasksRemoteDataSource,原理便是當需要用到TasksRemoteDataSource時,
不會真正使用該類,而是注入FakeTasksRemoteDataSource,返回事先定義好的數據。
mvp單元測試總結
MVP各層之間的職責以及對應的測試內容,接下來做個總結,MVP測試架構圖
View層
職責:MVP模式下,View層終於揚眉吐氣了,View本身該做的事情都能做了,比如UI佈局,數據渲染,點擊按鈕交互等等
測試方式:以正常小QA的測試思維方法,就可以來定義這一層的測試方式,測試過程中需要真機或模擬器,並做真實的操作。
測試選型:依賴於Android環境,用谷歌強大的Espresso+AndroidJUnitRunner,Espresso用於模擬和驗證各種各樣的UI操作,代碼存放於AndroidTest中。
Presenter層:
職責:這一層是拉皮條的,負責M和V層的對接,所以有較少的處理輸入輸出的機會,他只用來控制邏輯,去調用相應的Model和View的邏輯。
測試選型:他的職責決定了他很少去斷言輸入輸出,測試邏輯覆蓋的路徑是否正確即可,因此他與Android環境無關,用Junit+Mockito測試即可,代碼存放於test中。
Model層
職責:負責數據的存取,數據可能來自於網絡、數據庫和內存
數據庫增刪改查:需測試數據存取的準確性,依賴Android環境進行測試,因此使用AndroidJUnitRunner,代碼存放於androidTest中
網絡請求:不測試真實的網絡請求,但提供了Fake供其他層調用測試。
封裝的門面類:決定了數據的來源和去向是來自於本地數據庫 or 網絡 or 內存,此爲真正對其他層暴露的Model類。此類不做數據準確性的驗證,只做mock測試,驗證覆蓋路徑。
UT選型Junit+Mockito,代碼存放於test中。
總結
寫單元測試前期雖然比較複雜和麻煩,但是這樣谷歌官方的寫法思路是非常清晰的,非常值得學習,可以減少很多測試成本,減少錯誤率。