這篇文章為《解讀Android官方MVP項(xiàng)目單元測試》(以下簡稱《解讀》)的附錄部分,行此文的目的有二,其一是這個(gè)項(xiàng)目的單元測試齊全,覆蓋率很高,有極高的學(xué)習(xí)價(jià)值,筆者希望把每個(gè)測試用例都描述一遍,通過這種方式來強(qiáng)迫自己認(rèn)真的看完。其二,這部分內(nèi)容難免枯燥,筆者盡力想把它寫得可讀性高一點(diǎn)點(diǎn),卻發(fā)現(xiàn)著實(shí)有難度,簡直是給自己挖坑,所以從《寫點(diǎn)有價(jià)值的測試用例》的角度出發(fā),對(duì)這篇附錄文章稍作修飾。但不管這樣,這個(gè)MVP項(xiàng)目及其單元測試用例對(duì)我們的工作可以帶來很多思路,所以不妨速讀一遍。
一 什么是有價(jià)值的測試用例
以這個(gè)項(xiàng)目為例,我覺得對(duì)于測試用例的設(shè)計(jì),不能離開架構(gòu)層面和業(yè)務(wù)層面。
- 架構(gòu)層面
不同的架構(gòu),決定著測試用例的寫法不一,比如MVC或者M(jìn)VP,每一層負(fù)責(zé)的測試職責(zé)是不一樣的。 以todo-mvp這個(gè)項(xiàng)目為例,筆者在《解讀》中已經(jīng)提到,一個(gè)功能的測試需要MVP三層的協(xié)作,彼此各司其職,卻又互相聯(lián)系,這里再做一番總結(jié):
- Presenter層:這一層很清晰,我們?yōu)樗拿總€(gè)接口方法,以及每個(gè)方法里涉及的多個(gè)邏輯路徑設(shè)計(jì)相應(yīng)的測試用例,值得注意的是,這一層我們較少做輸入輸出的斷言,而是驗(yàn)證是否正確覆蓋V層和M層的邏輯。
- Model層:同上,我們?yōu)樗拿總€(gè)方法設(shè)計(jì)測試用例,與P層不同,這一層要斷言輸入輸出數(shù)據(jù)是否準(zhǔn)確。
- View層:這一層我們放在業(yè)務(wù)層面來講。
- 業(yè)務(wù)層面
做單元測試,測試業(yè)務(wù)邏輯是重中之重。View層承擔(dān)著這一重任,設(shè)計(jì)這一層的測試用例時(shí),不要想太多,站在我們正常使用功能的角度出發(fā),把交互行為翻譯成Espresso的代碼。由于View是入口,當(dāng)一種交互行為發(fā)生,Presenter開始調(diào)度View和Model層各自執(zhí)行邏輯,因此從這個(gè)角度來講,View層的測試涵蓋了MVP三層的邏輯。
聊完有價(jià)值的,我們?cè)賮砜纯词裁词?strong>沒價(jià)值的測試用例。比如以下幾種:
- 對(duì)成熟的工具類進(jìn)行測試
- 對(duì)簡單的方法進(jìn)行測試(比如get、set方法)
- MVP各層重復(fù)測試,比如P層去斷言輸入輸出的正確性
接下來筆者將完整的展示這個(gè)MVP項(xiàng)目中的所有單元測試用例,分為三個(gè)維度,androidTest下的、androidTestMock下的和test下的所有測試用例,如果覺得閱讀起來枯燥,可以直接閱讀每個(gè)測試類開篇的概述部分。
二 androidTest文件下的測試
V層:導(dǎo)航界面測試——AppNavigationTest
概述:該測試用例做導(dǎo)航測試,即對(duì)DrawerLayout打開、關(guān)閉、點(diǎn)擊Item后打開的Activity等功能進(jìn)行測試。
意義:告訴我們?nèi)绾螌?duì)DrawerLayout設(shè)計(jì)有價(jià)值的測試用例。
-
clickOnStatisticsNavigationItem_ShowsStatisticsScreen
打開Left Drawer->點(diǎn)擊Statistics按鈕->斷言StatisticsActivity已經(jīng)打開 -
clickOnListNavigationItem_ShowsListScreen
打開Left Drawer->點(diǎn)擊Statistics按鈕->打開Left Drawer->點(diǎn)擊TO-DO list按鈕->斷言TasksActivity已經(jīng)打開 -
clickOnAndroidHomeIcon_OpensNavigation
驗(yàn)證通過ActionBar的icon進(jìn)行關(guān)閉和打開Left Drawer
V層:任務(wù)模塊界面測試——TasksScreenTest
概述:該測試用例針對(duì)任務(wù)列表和任務(wù)詳情頁的界面功能測試,涵蓋所有頁面上的交互,包括增刪改查任務(wù)、改變?nèi)蝿?wù)狀態(tài),過濾任務(wù)列表等,除此之外還驗(yàn)證了橫豎屏的交互對(duì)界面數(shù)據(jù)狀態(tài)的影響。
意義:告訴我們?nèi)绾卧O(shè)計(jì)有價(jià)值的功能界面測試用例。
-
clickAddTaskButton_opensAddTaskUi
點(diǎn)擊添加按鈕->斷言相應(yīng)Activity已經(jīng)打開 -
addTaskToTasksList
添加標(biāo)題1的TO-DO任務(wù)后回到列表頁->斷言標(biāo)題1存在 -
editTask
添加標(biāo)題1的TO-DO任務(wù)后回到列表頁->點(diǎn)擊此Item進(jìn)入查看頁面->點(diǎn)擊編輯按鈕->修改成標(biāo)題2->點(diǎn)擊保存->斷言標(biāo)題1不存在和標(biāo)題2存在 -
markTaskAsComplete
添加任務(wù)并點(diǎn)擊CheckBox設(shè)置為已完成->通過過濾器進(jìn)入All/Active/Completed視圖,斷言該任務(wù)是否存在 -
markTaskAsActive
測試標(biāo)記任務(wù)為Active狀態(tài),手法同上一點(diǎn) -
showAllTasks
添加2個(gè)任務(wù)->進(jìn)入All視圖->斷言兩個(gè)任務(wù)在界面上存在 -
showActiveTasks
添加2個(gè)任務(wù)->進(jìn)入Active視圖->斷言兩個(gè)任務(wù)在界面上存在 -
showCompletedTasks
添加2個(gè)任務(wù)->標(biāo)記為已完成->進(jìn)入Completed視圖->斷言兩個(gè)任務(wù)在界面上存在 -
clearCompletedTasks
添加2個(gè)任務(wù)->標(biāo)記為已完成->點(diǎn)擊Clear completed按鈕->斷言兩個(gè)任務(wù)不存在 -
createOneTask_deleteTask
添加1個(gè)任務(wù)->點(diǎn)擊該任務(wù)進(jìn)入詳情頁->點(diǎn)擊刪除->斷言該任務(wù)不存在 -
createTwoTasks_deleteOneTask
創(chuàng)建2個(gè)任務(wù)->刪除第2個(gè)->斷言第1個(gè)存在且第2個(gè)不存在 -
markTaskAsCompleteOnDetailScreen_taskIsCompleteInList
創(chuàng)建1個(gè)任務(wù)->點(diǎn)擊該任務(wù)進(jìn)入詳情頁->標(biāo)記為已完成->回到列表頁->斷言該任務(wù)為選中狀態(tài) -
markTaskAsActiveOnDetailScreen_taskIsActiveInList
創(chuàng)建1個(gè)任務(wù)->在列表頁標(biāo)記為已選中->進(jìn)入詳情頁標(biāo)記為未選中->回到列表頁->斷言該任務(wù)未被選中 -
markTaskAsAcompleteAndActiveOnDetailScreen_taskIsActiveInList
創(chuàng)建1個(gè)任務(wù)->在詳情頁觸發(fā)兩次checkbox的點(diǎn)擊- 回到列表頁->斷言該任務(wù)未被選中 -
markTaskAsActiveAndCompleteOnDetailScreen_taskIsCompleteInList
創(chuàng)建1個(gè)任務(wù)->標(biāo)記為已完成->在詳情頁觸發(fā)兩次checkbox的點(diǎn)擊- 回到列表頁->斷言該任務(wù)被選中 -
orientationChange_FilterActivePersists
創(chuàng)建1個(gè)任務(wù)->標(biāo)記為已完成->進(jìn)入Active視圖->驗(yàn)證該任務(wù)不存在->切換橫豎屏->斷言該任務(wù)狀態(tài)與之前一致 -
orientationChange_FilterCompletedPersists
創(chuàng)建1個(gè)任務(wù)->標(biāo)記為已完成->進(jìn)入Completed視圖->驗(yàn)證該任務(wù)存在->切換橫豎屏->斷言該任務(wù)狀態(tài)與之前一致
M層:本地?cái)?shù)據(jù)庫操作測試——TasksLocalDataSourceTest
概述:對(duì)數(shù)據(jù)庫中處理Task的增刪改查、改變Task狀態(tài)等進(jìn)行測試。
意義:持久層的CRUD往往需要配合起來測試和斷言,此例很好的詮釋了這一點(diǎn)
saveTask_retrievesTask
- 測試目的:驗(yàn)證保存Task到數(shù)據(jù)庫的邏輯
- 測試用例:實(shí)例化Task對(duì)象->保存入庫->根據(jù)ID獲取Task->在回調(diào)函數(shù)中斷言與入庫的Task一致
completeTask_retrievedTaskIsComplete
- 測試目的:驗(yàn)證將任務(wù)設(shè)置成完成狀態(tài)的邏輯
- 測試用例:Task對(duì)象保存入庫->觸發(fā)完成任務(wù)的邏輯->根據(jù)ID獲取Task->在回調(diào)函數(shù)中斷言該Task已完成
activateTask_retrievedTaskIsActive
- 測試目的:驗(yàn)證將任務(wù)設(shè)置為激活狀態(tài)的邏輯
- 測試用例:mock一個(gè)回調(diào)對(duì)象callback->Task對(duì)象保存入庫->觸發(fā)完成任務(wù)的邏輯->觸發(fā)激活任務(wù)的邏輯->根據(jù)ID獲取Task->斷言callback執(zhí)行了onTaskLoaded的邏輯
clearCompletedTask_taskNotRetrievable
- 測試目的:驗(yàn)證清除所有已完成任務(wù)的邏輯
- 測試用例:mock三個(gè)回調(diào)函數(shù)對(duì)象,分別是callback1到3->保存任務(wù)1,任務(wù)2和任務(wù)3,其中任務(wù)1和任務(wù)2為completed狀態(tài),任務(wù)3為active狀態(tài)->清理所有已完成的任務(wù)->根據(jù)3個(gè)Task的ID分別獲取Task->斷言callback1和callback2執(zhí)行了onDataNotAvailable邏輯- >斷言callback3執(zhí)行onTaskLoaded邏輯
deleteAllTasks_emptyListOfRetrievedTask
- 測試目的:驗(yàn)證刪除數(shù)據(jù)庫中所有任務(wù)的邏輯
- 測試用例:保存任務(wù)->mock一個(gè)回調(diào)函數(shù)callback->刪除所有任務(wù)->獲取任務(wù)列表->斷言callback執(zhí)行了onDataNotAvailable的邏輯
getTasks_retrieveSavedTasks
- 測試目的:驗(yàn)證獲取數(shù)據(jù)庫中所有任務(wù)的邏輯
- 測試用例:保存2個(gè)任務(wù)->獲取任務(wù)列表->在回調(diào)函數(shù)中斷言這2個(gè)任務(wù)存在
三 androidTestMock文件下的測試
在《解讀》一文中,筆者提到該文件夾主要的作用是對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行Fake,即不發(fā)出網(wǎng)絡(luò)請(qǐng)求,而是返回事先定義好的數(shù)據(jù)。
V層:新增編輯任務(wù)界面測試——AddEditTaskScreenTest
errorShownOnEmptyTask
- 測試目的:驗(yàn)證保存或編輯任務(wù)時(shí),如果輸入空標(biāo)題,會(huì)彈出Snackbar提示不能為空
- 測試用例:打開詳情頁->輸入空標(biāo)題和空描述->點(diǎn)擊保存->通過Snackbar的消息內(nèi)容驗(yàn)證Snackbar已顯示
V層:統(tǒng)計(jì)界面測試——StatisticsScreenTest
-
Tasks_ShowsNonEmptyMessage
打開統(tǒng)計(jì)界面->事先Fake兩條任務(wù)數(shù)據(jù),狀態(tài)分別為Completed和Active->斷言兩種統(tǒng)計(jì)欄目都已顯示
V層:任務(wù)詳情界面測試——TaskDetailScreenTest
概述:Fake出不同狀態(tài)的任務(wù)并在詳情頁進(jìn)行標(biāo)題、描述和狀態(tài)的斷言。
意義:指導(dǎo)我們?nèi)绾螌?duì)網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)進(jìn)行Fake。
-
activeTaskDetails_DisplayedInUi
Fake一條狀態(tài)為Active的任務(wù)->打開詳情頁->斷言標(biāo)題、描述、任務(wù)狀態(tài) -
completedTaskDetails_DisplayedInUi
Fake一條狀態(tài)為Complete的任務(wù)->打開詳情頁->斷言標(biāo)題、描述、任務(wù)狀態(tài) -
orientationChange_menuAndTaskPersist
橫豎屏的測試手法與TasksScreenTest
中一致,不再贅述。
四 test文件夾下的測試
P層:新增編輯任務(wù)測試——AddEditTaskPresenterTest
概述:進(jìn)入Presenter層的測試后,我們不再去斷言輸入輸出了,取而代之的是,斷言是否正確的覆蓋了View層和Model層的邏輯。
AddEditTaskPresenter
共有三個(gè)方法,分別是createTask
、updateTask
和populateTask
,對(duì)應(yīng)增加、修改、展示任務(wù)的功能,其中增加任務(wù)涉及到成功和失敗的情況,因此有4個(gè)測試用例。
意義:這些Presenter層的測試可以教會(huì)我們?nèi)绾蜯ock,如何verify,如何測試異步回調(diào),以及如何完整的覆蓋Presenter層的所有邏輯路徑。
-
saveNewTaskToRepository_showsSuccessMessageUi
創(chuàng)建Presenter,執(zhí)行創(chuàng)建任務(wù)的邏輯->斷言Model層執(zhí)行了保存的邏輯->斷言View層執(zhí)行了顯示任務(wù)列表的邏輯 -
saveTask_emptyTaskShowsErrorUi
創(chuàng)建Presenter,執(zhí)行創(chuàng)建任務(wù)的邏輯,且任務(wù)Title為空->斷言View層執(zhí)行了展示error的邏輯 -
saveExistingTaskToRepository_showsSuccessMessageUi
此用例驗(yàn)證的是update任務(wù)的邏輯,測試手法同1。 populateTask_callsRepoAndUpdatesView
- 測試目的:驗(yàn)證詳情頁展示的任務(wù)信息是否正確
- 測試用例:presenter執(zhí)行populateTask()->斷言執(zhí)行了getTask(),且參數(shù)正確->斷言回調(diào)函數(shù)執(zhí)行了正確的邏輯->斷言View層展示的是正確的Task數(shù)據(jù)
P層:統(tǒng)計(jì)功能測試——StatisticsPresenterTest
概述:該類的presenter接口比較簡單,只有一個(gè)入口方法
start
,執(zhí)行的是加載統(tǒng)計(jì)信息的邏輯,執(zhí)行過程中涉及幾個(gè)路徑:加載空任務(wù)列表,加載非空任務(wù)列表和數(shù)據(jù)不可用,分別對(duì)應(yīng)以下1,2,3點(diǎn)。
-
loadEmptyTasksFromRepository_CallViewToDisplay
斷言加載空任務(wù)列表 -
loadNonEmptyTasksFromRepository_CallViewToDisplay
斷言加載非空任務(wù)列表 -
loadStatisticsWhenTasksAreUnavailable_CallErrorToDisplay
斷言數(shù)據(jù)不可用
P層:任務(wù)詳情功能測試——TaskDetailPresenterTest
概述:該P(yáng)resenter共有5個(gè)方法,分別是:
-
start
:展示任務(wù)詳情,涉及三種路徑:展示Active任務(wù)、展示Compeled任務(wù)和展示非法ID的任務(wù),對(duì)應(yīng)1,2,3的測試用例 -
deleteTask
:刪除任務(wù),對(duì)應(yīng)第4個(gè)測試用例 -
completeTask
:完成任務(wù),對(duì)于第5個(gè) -
activateTask
:激活任務(wù),對(duì)應(yīng)第6個(gè) -
editTask
:編輯任務(wù),對(duì)應(yīng)第7個(gè),編輯非法ID的任務(wù)對(duì)應(yīng)的測試用例為第8個(gè)
getActiveTaskFromRepositoryAndLoadIntoView
getCompletedTaskFromRepositoryAndLoadIntoView
getUnknownTaskFromRepositoryAndLoadIntoView
deleteTask
completeTask
activateTask
activeTaskIsShownWhenEditing
invalidTaskIsNotShownWhenEditing
P層:任務(wù)列表功能測試——TasksPresenterTest
概述,此TasksPresenter的測試與上一點(diǎn)類似,從接口方法出發(fā),此類共有10個(gè)接口方法,為此設(shè)計(jì)了8個(gè)測試用例,分別是展示All/Active/Completed的任務(wù)列表、點(diǎn)擊打開任務(wù)詳情頁、改變?nèi)蝿?wù)狀態(tài)等。
loadAllTasksFromRepositoryAndLoadIntoView
loadActiveTasksFromRepositoryAndLoadIntoView
loadCompletedTasksFromRepositoryAndLoadIntoView
clickOnFab_ShowsAddTaskUi
clickOnTask_ShowsDetailUi
completeTask_ShowsTaskMarkedComplete
activateTask_ShowsTaskMarkedActive
unavailableTasks_ShowsError
M層:數(shù)據(jù)操作門面類測試——TasksRepositoryTest
概述:該類的測試用例非常齊全,對(duì)于如何設(shè)計(jì)測試用例讓數(shù)據(jù)過期,如何讓數(shù)據(jù)取自本地或者網(wǎng)絡(luò)等測試技巧都有極高的學(xué)習(xí)價(jià)值。
getTasks_repositoryCachesAfterFirstApiCall
getTasks_requestsAllTasksFromLocalDataSource
saveTask_savesTaskToServiceAPI
completeTask_completesTaskToServiceAPIUpdatesCache
completeTaskId_completesTaskToServiceAPIUpdatesCache
activateTask_activatesTaskToServiceAPIUpdatesCache
activateTaskId_activatesTaskToServiceAPIUpdatesCache
getTask_requestsSingleTaskFromLocalDataSource
deleteCompletedTasks_deleteCompletedTasksToServiceAPIUpdatesCache
deleteAllTasks_deleteTasksToServiceAPIUpdatesCache
deleteTask_deleteTaskToServiceAPIRemovedFromCache
getTasksWithDirtyCache_tasksAreRetrievedFromRemote
getTasksWithLocalDataSourceUnavailable_tasksAreRetrievedFromRemote
getTasksWithBothDataSourcesUnavailable_firesOnDataUnavailable
getTaskWithBothDataSourcesUnavailable_firesOnDataUnavailable
getTasks_refreshesLocalDataSource