天天看點

[譯] 使用 Espresso 和 Mockito 測試 MVP使用 Espresso 和 Mockito 測試 MVP

<b>本文講的是[譯] 使用 Espresso 和 Mockito 測試 MVP,</b>

作為軟體開發者,我們盡最大努力做正确的事情確定我們并非無能,并且讓其他同僚以及上司信任我們所寫的代碼。我們遵守最好的程式設計習慣、使用好的架構模式,但是有時發現要确切的測試我們所寫的代碼很難。

就個人而言,我發現一些開源項目的開發者非常善于打造令人驚歎的産品(可以打造任何你可以想象的應用),但是由于某些原因缺乏編寫正确測試的能力,甚至一點都沒有。

本文是關于如何對廣泛應用的 MVP 架構模型進行單元測試的簡單教程。

那麼這個應用究竟長什麼樣呢?

這是一個非常簡單的 Android 應用,它隻做一件事:當點選按鈕時隐藏或者顯示一個 TextView。

這是應用起初的樣子:

Initial

這是按鈕點選後的樣子:

724E8fE.png

出于文章的需要,我們假設這是一個價值數百萬的産品,并且它現在的樣子将會持續很長時間。一旦發生變化,我們需要立刻知曉。

應用中有三部分内容:一個有應用名的藍色工具欄,一個顯示 “Hello World” 的 TextView,以及一個控制 TextView 顯隐的按鈕。

我們開始吧!

我們首先對炫酷的 ToolBar 進行測試。畢竟是一個價值數百萬的應用,我們需要確定它的正确性。

如下是測試 ToolBar 的完整代碼。如果你看不懂這到底是什麼鬼,也沒關系,後面我們一起過一下。

首先,我們需要告訴 JUnit 所執行測試的類型。對應于第一行代碼(@runwith (AndroidJUnit4.class))。它這樣聲明,“嘿,聽着,我将在真機上使用 JUnit4 進行 Android 測試”。

測試代碼存放在 androidTest 目錄。

android_test_directory

下面我們看一下 “ActivityTestRule”,如下 Android 文檔做出了詳細的介紹:

本質上是說,“這是我要測試的 Activity”。

下面我們具體看下 testToolBarDesign() 方法具體做了什麼。

這段測試代碼是找到 ID 為 “R.id.toolbar” 的 view,然後檢查它的可見性。如果本行代碼執行失敗,測試會立刻結束并不會進行其餘的測試。

這行是說,“嘿,讓我們看看是否有文本内容為 R.string.app_name 的 textView ,并且看看它的父 View 的 id 是否為 R.id.toolbar”。

最後一行的測試更有趣一些。它是要确認 toolbar 的背景色是否和應用的首要顔色一緻。

在講代碼之前,我需要說下代碼有點長,但是十分易讀。我對代碼内容作了詳細注釋。

這段代碼主要功能是保證應用打開時,ID 為 “R.id.tv_to_show_hide” 的 TextView 處于顯示狀态,并且其顯示内容為 “Hello World!”

然後檢查按鈕也是顯示狀态,并且其文案(預設)顯示為 “Hide”。

接着點選按鈕。點選按鈕十分簡單,如何實作的也十分易懂。這裡我們對找到相應 ID 的 view 執行 .perform() (而非 “.check”),并且在其内執行 click() 方法。perform() 方法實際是執行傳入的操作。這裡對應是 click() 操作。

因為點選了 “Hide” 按鈕,我們需要驗證 TextView 是否真的隐藏了。具體做法是在 disDisplayed() 方法前置一個 “not()”,并且按鈕文案變為 “Show”。其實這就和 java 中的 “!=” 操作符一樣。

後面的代碼是前面代碼的反轉。再次點選按鈕,驗證 TextView 重新顯示,并且按鈕文案符合目前狀态。

就這些。

如下是全部的 UI 測試代碼:

單元測試最大特點是在本機的 JVM 環境上運作(與 Android 測試不同)。無需連接配接裝置,測試跑的也更快。缺點就是無法通路 Android 架構 API。總之進行 UI 之外的測試時,盡量使用單元測試而非 Android/Instrumentation 測試。測試運作的越快越好。

下面我們看下單元測試的目錄。單元測試的位置與 Android 測試不同。

different_location

開始前我們先看下 presenter 以及關于 model 需要考慮的問題。

很簡單。兩個方法:一個檢查 view 是否可見。如果可見就隐藏它,反之顯示。之後将按鈕的文案改為 “Hide” 或 “Show”。

reverseViewVisibility() 方法調用 “model” 對傳入的 view 進行可見性設定。

兩個方法:showView(View) 和 hideView(View)。具體功能十分直覺。檢查 view 是否為 null,不為 null 則對其進行顯隐設定。

現在我們對 presenter 和 model 都有所了解了,下面我們開始測試。畢竟這是一個數百萬的産品,我們不能有任何錯誤。

我們首先測試 presenter。當使用 presenter (任何 presenter)時,我們需要確定 view 已與之關聯。注意:我們并不測試 view。我們隻需要確定 view 的綁定以便确認是否在正确的時間調用了正确的 view 方法。記住,這很重要。

這裡我們使用 Mockito 進行測試,就像單元測試那樣,我們需要告訴 Android,“嘿,我們需要使用 MockitoJUnitRunner 進行測試。”實際操作時在測試類的頂部添加 @RunWith (MockitoJUnitRunner.class) 即可。

從前面可知我們需要兩個東西:一是模拟一個 View (因為 presenter 使用了 View 對象,對其進行顯隐控制),另外一個是 presenter。

下面展示了如何使用 Mockito 進行模拟

我們要寫的第一個測試是 “testReverseViewVisibilityFromVisibleToGone”。顧名思義,我們将要驗證的是,當可見的 View 被傳入 presenter 的 reverseViewVisibility() 方法時,presenter 能正确地設定 View 的可見性。

我們一起看下,這裡具體做了什麼?由于我們要測試的是 view 從可見到不可見的操作,我們需要 view 一開始是可見的,是以我們希望一開始調用 view 的 isShown() 方法傳回是 true。接着,以模拟的 view 作為入參調用 presenter 的 reverseViewVisibility() 方法。現在我們需要确認 view 最近被調用的方法是 setVisibility(),并且設定為 GONE。然後,我們需要确認與 presenter 綁定的 view 的 setButtonText() 方法是否調用。并不難吧?

嗯,接着我們進行相反的測試。在繼續閱讀下面的代碼之前,試着自己想一下怎麼做。如何測試從隐藏到顯示的情況?根據上面已知的資訊思考一下。

代碼實作如下:

接着測試 “Model”。和前面一樣,我們首先在類頂部添加注解 @RunWith (MockitoJUnitRunner.class) 。

如前面所說,Utils 類首先檢查 view 是否為 null。如果不為 null 将執行顯隐操作,反之什麼都不會做。

Utils 類的測試十分簡單,是以我不再逐行解釋,大家直接看代碼即可。

我解釋下 testShowViewWithNullView() 和 testHideViewWithNullView() 方法的作用。為什麼要進行這些測試?試想下,我們不希望因為 view 為 null 時調用方法造成整個應用的崩潰。

我們看下 Utils 的 showView() 方法。如果不做 null 檢查,當 view 為 null 時應用會抛出 NullPointerException 并崩潰。

另外一些情況下,我們需要應用抛出一個異常。我們如何測試一個異常?十分簡單:隻需要對 @Test 注解傳遞一個 expected 參數進行指定:

如果沒有異常抛出,該測試會失敗。

本文接近尾聲,需要提醒大家的是:測試并不總是像本例這樣簡單,但也不意味着不會如此或不該如此。作為開發者,我們需要確定應用正确的運作。我們需要確定大家信任我們的代碼。我已經持續這樣做許多年了,你可能無法想象測試拯救了我多少次,甚至是像改變 view ID 這樣最簡單的事。

沒有人是完美的,但是測試讓我們趨近完美。保持編碼,保持測試,直到永遠!

<b></b>

<b>原文釋出時間為:2017年5月24日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀