在 MVP 中進行單元測試

對于測試粪狼,大家都不陌生纺念,但是我相信還是有部分開發(fā)覺得測試工作和自己沒有直接關系拷泽、測試工作是測試工程師的事。慚愧的說杉武,本人也是很長一段時間內(nèi)沒真正理解“測試”這件事兒,之前呆過的幾家公司都沒有真正的“測試工程師”辙售,確切的說轻抱,是沒有會寫代碼的測試工程師,基本上都是手動測試旦部,然后輸出報告祈搜,測試無需懂技術,我相信國內(nèi)很多公司都是這樣志鹃,特別是 App 端的測試夭问,很少有白盒測試的。這篇要說的東西不多曹铃,主要來說說單元測試缰趋,由于本人也是最近才開始實踐,文章拋磚引玉陕见,如果有說得不到位的地方秘血,希望讀者給予指正。

0. 提綱

  • 0.1 什么是單元測試
  • 0.2 單元測試的意義
  • 0.3 Android 開發(fā)中進行單元測試
  • 0.3.1 AndroidStudio 中配置
  • 0.4 開源框架
  • 0.5 MVP 架構中如何寫單元測試
  • 0.6 例子

1. 什么是單元測試

我們先來做這么一個體驗评甜。假設現(xiàn)在我們有個開發(fā)者要完成某個功能的開發(fā)灰粮,現(xiàn)在我們從電腦屏幕的視角來觀察他接下來的一序列行為:

   他創(chuàng)建了一個類:A。
    光標在同一個地方閃爍了好久忍坷,他應該在思考 ....
    他在 A 寫了一個方法 a粘舟。
    接著寫了方法b、c佩研、d柑肴。
    寫的有點多了,滾動條來回滑動旬薯,他在閱讀代碼梳理邏輯晰骑。
    他打開了啟動器,運行了A绊序。
    運行報錯了硕舆。
    他繼續(xù)修改方法。
    再次打開啟動器骤公,運行了A抚官。
    運行成功了,他收獲了滿足感淋样。
    ......

在這個觀察中耗式,我們看到他每次啟動運行 A 所做的事其實就是“測試”,所以單元測試這件事其實是每個開發(fā)都做得最多的一件事,只是怎么讓這件事變得更高效刊咳、更有意義是接下來要講的彪见。最后給出一個官方性的定義:單元測試又稱為模塊測試, 是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。 程序單元是應用的最小可測試部件娱挨。

2. 單元測試的意義

我們先來看看一張圖:

測試金字塔

這張圖叫做測試金字塔余指,單元測試處于開發(fā)環(huán)節(jié)比較早期,粒度也比較小跷坝,也是最重要的一個環(huán)節(jié)酵镜,對整體測試的效率至關重要,這部分做好了柴钻,后期的測試才會更有效淮韭,試想如果整棟樓建好了才發(fā)現(xiàn)磚頭質(zhì)量有問題,那是多么糟糕的事贴届;單元測試就像對每個螺絲釘測試一樣靠粪,生產(chǎn)螺絲釘?shù)臅r候發(fā)現(xiàn)螺絲釘有問題很容易解決,如果在機器運轉(zhuǎn)故障的時候去定位是不是螺絲釘毫蚓、具體是那顆螺絲釘就難了占键。單元測試的意義可以總結如下幾點:

  • 代碼質(zhì)量的基礎保障。
  • 單元測試元潘,模塊區(qū)間比較小畔乙,更容易發(fā)現(xiàn)問題,發(fā)現(xiàn)問題更容易定位翩概。
  • 為后期集成測試節(jié)省了很多時間牲距。
  • 單元測試是對程序結構的一個觀察者視角審視,有利于我們提高代碼的整體設計能力钥庇。

3. Android 開發(fā)中進行單元測試

3.1 AndroidStudio 中配置

Android 為我們提供了兩種跑測試的方式:

  • Unit Tests
  • Android Instarumentaion Tests
模式選擇

3.1.1 Unit Tests

Unit Tests 這種方式嗅虏,跑的測試代碼運行在本機 JVM 上,不需要編譯Apk 上沐,不需要 Android 設備的支持,速度相對快楞艾,當然参咙,測試的對象不能含有 Android 的 API,否則運行時會報錯硫眯。這個模式下蕴侧,測試代碼的默認路徑是 test/java。單元測試的基礎框架是 Junit两入,需要配置依賴:

    testCompile "junit:junit:4.12"

然后我們來寫一個被測試類:Calculator.java

public class Calculator {
        public  int add(int a, int b) {
            return a - b;
        }
}

把Test Artifact 切換到 Unit Tests 模式净宵,然后我們創(chuàng)建一個針對這個類的測試:CalculatorTest.java

goto

選擇 create new test

goto
public class CalculatorTest {

    @Before
    public void setUp() throws Exception {
            
    }
    @After
    public void tearDown() throws Exception {
            
    }
    @Test
    public void testAdd() throws Exception {

    }
    
}

自動生成了選擇的測試方法,@Before、 @After择葡、@Test 這幾個注解來自 Junit 的 API紧武,@Before 標記的方法,在測試用例啟動時最先執(zhí)行敏储,然后執(zhí)行 @Test 標記的方法阻星,最后執(zhí)行 @After 標記的方法。一般會在 @Before 的方法中做初始化工作已添,例如:創(chuàng)建被測試的對象妥箕, @Test 標記的就是我們要測試的方法,在其中執(zhí)行驗證方法更舞。完善我們的測試代碼:

public class CalculatorTest {

    private Calculator calculator;
    @Before
    public void setUp() throws Exception {
               calculator = new Calculator();
    }
    @After
    public void tearDown() throws Exception {
               
    }
    @Test
    public void testAdd() throws Exception {
                // 驗證方法執(zhí)行結果畦幢,1+1 是否等于 2
       assertEquals(2, calculator.add(1 , 1));
    }
    
}

然后在這個類上右鍵,我們就可以運行這個測試類了缆蝉。

3.1.1 Android Instarumentaion Tests

Android Instarumentaion Tests 包的測試代碼運行在 Android 設備上宇葱,速度相對慢一些。測試代碼默認路徑是 androidTest/java返奉。為了讓測試能跑在設備上贝搁,需要 AndroidJUnitRunner 的支持:

defaultConfig {
    applicationId "com.dalimao.demoforunittest"
    minSdkVersion 15
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
            // support Instarumentaion Tests
    testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
 }

配置依賴:

androidTestCompile "com.android.support.test:runner:0.4.1"

同時也離不開 Junit 的支持,配置依賴:

 androidTestCompile "junit:junit:4.12"

創(chuàng)建測試和 Unit Tests 的一樣芽偏,運行的時候需要連接模擬器或手機雷逆。

4 開源框架

測試框架很多,這里只簡單說說比較流行的組合以及本例子中要使用的框架:

  • Junit 是最簡單最基本的 Java 單元測試框架污尉。
  • Mockito 強大的 Java 測試框架膀哲,強大之處在于能很方便的模擬對象,改變對象行為被碗,同時提供了很多驗證API某宪。
  • <font color = gray>espresso</font>:google 提供的 U I測試框架。

5. MVP 架構中如何寫單元測試

理解了 AndroidStudio 中如何寫最基本的單元測試锐朴,再結合幾個開源框架兴喂,我們可以來寫一些更為貼近實際開發(fā)的測試用例了。 目前 MVP 是 Android 項目的比較流行的架構模式焚志,它的優(yōu)點在代碼的易測性
上也得到了極好的體現(xiàn)衣迷,對 MVP 不熟悉的考驗參考我之前的幾篇博客:

對于 MVP 個層次我們怎么來寫測試代碼? 先來看看框架的選型:

View層

View的測試自然依賴于Android環(huán)境酱酬,同時需要模擬U交互的能力壶谒,使用 Espresso + JUnit,Espresso用于模擬和驗證各種各樣的UI操作膳沽,使用 Android Instarumentaion Tests 運行汗菜,代碼存放于AndroidTest中让禀。

Presenter層

Presenter 上對UI,下對 Model 都是接口抽象陨界,控制流程邏輯巡揍,不涉及 Android API,用 Junit + Mockito 測試即可普碎,使用 Unit Tests 運行吼肥,代碼存放于test中。

Model層

負責數(shù)據(jù)的存取麻车,數(shù)據(jù)可能來自于網(wǎng)絡缀皱、數(shù)據(jù)庫和內(nèi)存,如果涉及設備相關的API动猬、依賴Android系統(tǒng)環(huán)境啤斗,則使用 Android Instarumentaion Tests 運行,代碼存放于androidTest中赁咙;反之可以Junit+Mockito钮莲,使用 Unit Tests 運行,代碼存放于test中彼水。

6. 例子

6.1 場景介紹

這是一個簡單的場景崔拥,一個游戲列表頁面。對于列表頁面凤覆,通常都會有緩存邏輯链瓦,加載數(shù)據(jù)時會優(yōu)先去加載本地數(shù)據(jù),如果沒有盯桦,則加載遠端數(shù)據(jù)慈俯。本例使用 MVP 來架構這個簡單的場景(引入了 EventBus 來代替常規(guī)的異步回調(diào),EventBus 極其簡單拥峦,不過不理解也不影響對本章主題的理解)贴膘,然后真對 M 和 P 分別寫了對于的單元測試,本例沒有寫 View 層的單元測試(個人覺得對 View 寫單元測試的必要性不大略号,如果你的感受相反刑峡,非常感謝你給我一個說服我的理由),我們先來流程圖:

流程圖

至此玄柠,建議先你下載 demo 源代碼氛琢,再一起完成下面的理解。認真的人不會找不到源碼地地址

6.2 目錄結構

目錄結構
  • test/java :該目錄下寫了針對 GamePresenter 的單元測試 GamePresenterTest 和針對 GameManager 的測試類 GameManagerTest (這個跑不過随闪,因為涉及類 Android 的 API)

  • androidTest / java: 該目錄下寫 針對 GameManager 的測試類 GameManagerAndroidTest (邏輯和 GameManagerTest 一樣)

6.3 例子中涉及 Mockito 的注解和說明

@Mock :對成員變量的注解,被這個注解的變量不用手動實例化骚勘,Mockito 會幫你注入一個模擬的對象(需要在 @Before 標記的方法中先執(zhí)行 MockitoAnnotations.initMocks(this)铐伴。例如:

   @Mock
 private IGameManager gameManager;

初始化 MockitoAnnotations

public void setUp() throws Exception {
    //Log 是 Android 的 API,Unit test 模式下關閉使用
    LogUtil.setDebug(false);
    // 初始化 @Mock 注解功能,自動注入 @Mock 標記的對象
    MockitoAnnotations.initMocks(this);
    //gameManager 已經(jīng)實例化
   
}

verify(T):這是 Mockito API 提供的一個靜態(tài)方法撮奏,用來驗證某個 mock 對象(@Mock 標記的對象)是否執(zhí)行了某個方法,執(zhí)行了多少次当宴。verify(T)畜吊,返回是一個泛型,你傳入什么類型他就返回什么類型的子類給你户矢,重寫的方法中驗證傳入對象對應的方法是否執(zhí)行過玲献。例如:

//驗證 gameListView.showLoading() 是否執(zhí)行
verify(gameListView).showLoading();

when(Object.method()).thenReturn(ReturnObject): 這是 Mockito API 提供的一個靜態(tài)方法,用來改變方法的對象的行為(方法)梯浪,當 Object.method() 調(diào)用的時候返回 ReturnObject 捌年,用來模擬方法的返回值。例如本例中模擬獲取本地數(shù)據(jù)為空:

   //模擬本地數(shù)據(jù)為空
    when(localGameDataSource.getData()).thenReturn(null);

認真的人不會找不到源碼地地址挂洛,歡迎留言礼预。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虏劲,隨后出現(xiàn)的幾起案子托酸,更是在濱河造成了極大的恐慌,老刑警劉巖柒巫,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件励堡,死亡現(xiàn)場離奇詭異,居然都是意外死亡堡掏,警方通過查閱死者的電腦和手機应结,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來布疼,“玉大人摊趾,你說我怎么就攤上這事∮瘟剑” “怎么了砾层?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贱案。 經(jīng)常有香客問我肛炮,道長,這世上最難降的妖魔是什么宝踪? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任侨糟,我火速辦了婚禮,結果婚禮上瘩燥,老公的妹妹穿的比我還像新娘秕重。我一直安慰自己,他們只是感情好厉膀,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布溶耘。 她就那樣靜靜地躺著二拐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凳兵。 梳的紋絲不亂的頭發(fā)上百新,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音庐扫,去河邊找鬼饭望。 笑死,一個胖子當著我的面吹牛形庭,可吹牛的內(nèi)容都是我干的铅辞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼碘勉,長吁一口氣:“原來是場噩夢啊……” “哼巷挥!你這毒婦竟也來了?” 一聲冷哼從身側響起验靡,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤倍宾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胜嗓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體高职,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年辞州,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔锌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡变过,死狀恐怖埃元,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚狰,我是刑警寧澤岛杀,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站崭孤,受9級特大地震影響类嗤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辨宠,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一遗锣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗤形,春花似錦精偿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墓阀。三九已至,卻和暖如春拓轻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背经伙。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工扶叉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帕膜。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓枣氧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垮刹。 傳聞我的和親對象是個殘疾皇子达吞,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容