Android單元測試(Android官方MVP架構示例項目解析)

解讀Android官方MVP項目單元測試分析得很到位
——本篇以此爲基礎,有所補充

本文通過分析Android官方MVP項目中最基礎的todo-mvp/示例項目,來歸納如何測試。(本篇不會介紹此Demo的邏輯、源碼結構,請閱讀代碼之後再讀此文)

一、測試Presenter層

這裏只說主頁面的TasksPresenter中的loadTasks方法(獲取所有數據)
這裏寫圖片描述

從時序圖上看,loadTask執行的邏輯是:

  • 1.調用View層開啓進度條
  • 2.從Model層獲取待辦任務列表
  • 3.Model層以回調函數的形式返回數據
  • 4.調用View層關閉進度條
  • 5.調用View層顯示任務列表。

這5個步驟裏,每個步驟的邏輯是否準確是View層和Model層該測試的事情,對於Presenter層來講,他的測試任務是確保這5個步驟如期調用。爲了達成此目的,我們會採用Mockito.verify()的api進行測試,這個測試類是TasksPresenterTest,代碼如下:

@Test
public void loadAllTasksFromRepositoryAndLoadIntoView() {
        // 塞選當前展示數據是All視圖狀態
        mTasksPresenter.setFiltering(TasksFilterType.ALL_TASKS);
        // 第0步:加載數據
        mTasksPresenter.loadTasks(true);

        // Callback is captured and invoked with stubbed tasks
        // 第2步:驗證mTasksRepository調用了getTasks,並將真實的LoadTasksCallback捕獲到ArgumentCaptor<T>中
        verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
        // 第3步:調用捕獲的回調參數,主動調用onTasksLoaded,並傳入有效值
        mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);

        // Then progress indicator is shown
        // 驗證第1步:驗證走了現實loading方法,進度條顯示
        verify(mTasksView).setLoadingIndicator(true);
        // Then progress indicator is hidden and all tasks are shown in UI
        // 驗證第4步:驗證隱藏了loading,進度條關閉
        verify(mTasksView).setLoadingIndicator(false);

        // 這個也是參數捕獲器
        ArgumentCaptor<List> showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
        // 驗證第5步:View層顯示待辦任務列表,驗證數據爲之前預設的3條
        verify(mTasksView).showTasks(showTasksArgumentCaptor.capture());
        assertTrue(showTasksArgumentCaptor.getValue().size() == 3);
    }

總結:讓Presenter充當個合格的皮條客,去調用其他兩層的邏輯,在假設其他兩層代碼邏輯都是正確的前提下,做一些mock測試,儘可能覆蓋所有邏輯路徑。

二、測試View層

知識補充:

1. JUnit4中的@Rule完全解析

  • @Rule是JUnit4的新特性。利用@Rule我們可以擴展JUnit的功能,在執行case的時候加入測試者特有的操作,而不影響原有的case代碼:減小了特有操作和case原邏輯的耦合。
  • TestRule是一個工廠方法模式中的Creator角色——聲明工廠方法。
  • 由於工廠方法apply有參數base,因而TestRule將創建裝飾模式中的裝飾對象(Statement)。
  • 這裏寫圖片描述

AndroidJUnit4中的ActivityTestRule也是實現了TestRule,作用:

  • 執行每個@Test方法之前打開相應的Activity
startIntent = new Intent(Intent.ACTION_MAIN);
startIntent.setClassName(targetPackage,mActivityClass.getName());
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivityClass.cast(mInstrumentation.startActivitySync(startIntent));
  • 擴展了一些方法,在Activity創建之前以及之後的相應方法
beforeActivityLaunched();
afterActivityLaunched();
  • 確保模擬的操作均在UI線程中執行
getInstrumentation().runOnMainSync(new Runnable() {
                public void run() {
                    try {
                        mBase.evaluate();
                    } catch (Throwable throwable) {
                        exceptionRef.set(throwable);
                    }
                }
            });

2.Espresso的測試等待異步結果機制

開發者面臨的挑戰之一,是在編寫UI測試時需要等待異步計算或I/O操作完成。這裏使用Espresso測試框架來解決這個問題和學到的一些技巧。

Idling Resource

Espresso引入了Idling Resource的概念,它是一個簡單的接口:

它代表了被測應用程序的資源,這個資源在測試執行時可以在後臺異步工作。

接口定義了三個方法:

  • getName():必須返回代表idling resource的非空字符串;
  • isIdleNow():返回當前idlingresource的idle狀態。如果返回true,onTransitionToIdle()上註冊的ResourceCallback必須必須在之前已經調用;
  • registerIdleTransitionCallback(IdlingResource.ResourceCallback callback):通常此方法用於存儲對回調的引用來通知idle狀態的變化。
  • 創建完自定義的idling resource後,需要通過調用Espresso.registerIdlingResource(webViewIdlingResource)與Espresso註冊。

總結:Idling Resource其實是Espresso的一個用來等待異步耗時操作的一個指示器(是否是空閒狀態)、通知器(轉爲空閒狀態時,通知Espresso)

  • 先通過Espresso.registerIdlingResource在測試中將Idling Resource註冊給Espresso
  • 異步耗時操作之前調節自定義的Idling Resource中的參數,使得現在isIdleNow()返回false(即標記爲非空閒狀態)
  • 異步耗時操作結束時,修改自定義的Idling Resource中的參數,使得現在isIdleNow()返回true(即標記爲空閒狀態)
  • 再調用registerIdleTransitionCallback(ResourceCallback resourceCallback),Espresso回調的ResourceCallback參數的onTransitionToIdle()方法,告知Espresso狀態轉到空閒

開啓View層的測試

其實View層的測試是從QA角度出發,以測試用例爲基本單位,做得好甚至可以用Suite結合起來,做到自動化測試,Google下的絕大多數App其實都運用了這個測試框架進行UI測試。

  • 第一步:測試哪個頁面
@Rule
public ActivityTestRule<TasksActivity> mTasksActivityTestRule =
            new ActivityTestRule<TasksActivity>(TasksActivity.class);
  • 第二步:給Espresso註冊Idling Resource
@Before
public void registerIdlingResource() {
        Espresso.registerIdlingResources(
                mTasksActivityTestRule.getActivity().getCountingIdlingResource());
    }

實際代碼中自定義的Idling Resource需要在特定地方進行標記

  • 第三部開始測試

站在QA的角度,我們想要驗證待辦任務列表時候,會設計以下的測試用例:
這裏寫圖片描述

請點擊此入口進入這部分內容

總結:這一層的測試站在用戶的角度,進行黑盒測試,在功能層面測試輸入(用戶操作)輸出(用戶得到的界面反饋),其實已經粗糙的覆蓋了M和P的邏輯了,做好其實就是自動化測試了。

三、Model層的測試

這部分與UI無關,有部分Model層的邏輯,網絡、數據庫、本地文件等部分需要Android環境,所以:
- Model純邏輯部分:Local Test > JUnit4 + Mockito + Hamcrest > 存放test下
- Model需要Android環境部分:AndroidJUnit4 > 存放 androidTest 下

請點擊此入口進入這部分內容

這張圖不得不盜用下
這裏寫圖片描述

Model層注意(Fake、門面模式):

  • 網絡請求:不測試真實的網絡請求,但提供了Fake供其他層調用測試,其實是面相接口的空實現。
  • 封裝的門面類:決定了數據的來源和去向是來自於本地數據庫 or 網絡 or 內存,此爲真正對其他層暴露的Model類。此類不做數據準確性的驗證,只做mock測試,驗證覆蓋路徑。UT選型Junit+Mockito,代碼存放於test中。

MVP模式少了Dragger2總不那麼完美,下一篇結合Dragger2的單元測試會有點久

發佈了39 篇原創文章 · 獲贊 9 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章