Activity 測試比較特殊,它依賴於 Android instrumentation 框架。與其它組件不一樣 Activity 基於一些回調函數有一個複雜的生命週期;除非用 instrumentation 咱們不能直接調用這些回調函數。另外,從程序向用戶接口發送事件的惟一方法也是用 instrumentation。html
該文檔描述了和用 instrumentation 和其它測試工具來測試 Activity。這裏假設你已經閱讀了 Testing Fundamentals 中介紹 Andoid 測試和 instrumentation 框架的部分。java
The Activity Testing APIandroid
Activity 測試的基類就是 InstrumentationTestCase,它爲用於測試 Activity 的 test case 子類提供 instrumentation。app
這個基類爲針對 Activity 的測試提供如下功能:框架
經過繼承 TestCase 和 Asser,這些 Activity 測試類一樣有 JUnit 框架的功能。eclipse
主要的兩個測試子類是 ActivityInstrumentationTestCase2 和 ActivityUnitTestCase。針對那些經過非標準模式加載的 Activity ,咱們要用 SingleLaunchActivityTestCase 來測試。ide
ActivityInstrumentationTestCase2函數
ActivityInstrumentationTestCase2 這個 test case 類用一個通用的底層系統來對一個應用程序中的一個或多個 Activity 進行功能測試。它用一個標準的系統 Context(非模擬的),在待測應用的一個通用實例(非模擬的)中運行 Activity。它容許你向待測 Activity 發送模擬 Intent,所以用過它,你能夠對一個能響應多種 intent 的 Activity 進行測試,或這對一個想從 Intent 中取得某種數據的Activity 進行測試,又或者兩種功能都有的 Activity 也能夠。可是要注意,因爲它不容許模擬 Context ,Application 對象,因此測試不能獨立於生產系統。工具
ActivityUnitTestCase單元測試
ActivityUnitTestCase 這個 test case 類用來對一個 Activity 進行獨立測試。在啓動 Activity 以前,能夠注入一個模擬 Context 或者 Application。經過它咱們能夠在一個獨立的環境中運行一個 Activity 測試,能夠對一些和 Android 系統沒有交互的函數進行單元測試。儘管經過調用 Activity.startActivity(Intent) 函數咱們能夠查看所接收的參數,可是咱們不能發送一個模擬 Intent 給待測 Activity。
SingleLanunchActivityTestCase
SingleLanunchActivityTestCase 能夠很方便地對單個 Activity 進行測試,而且所在的測試環境不會在過程當中被重置。它對 setUp() 和 tearDown() 的調用只有一次,而不是對每一個測試函數都調用一次。它不容許注入任何模擬對象。
這個 test case 更有利於測試那些運行在非 standard 模式的 Activity。它能夠確保測試的 fixture 不會在測試的工程中被重置。因此它能夠對 Activity 中多個相關聯的功能進行測試。
Mock objects and activity testing
這一部分介紹在 Activity 測試中模擬對象的用法,它們定義在 android.test.mock 包中。
模擬對象 MockApplication 僅用在使用 ActivityUnitTestCase 測試 Activity 的狀況。默認狀況下,ActivityUnitTestCase 會建立一個隱藏的 MockApplication 用來代替待測應用的 Application 對象。咱們能夠經過 setApplication() 函數注入本身的對象。
Assertions for activity testing
ViewAsserts 類中定義了一些針對 View 的斷言。能夠用它來驗證 View 對象的對齊方式和位置,而且能夠查看 ViewGroup 對象的狀態。
What To Test
Next Steps
要了解如何在 Eclipse 中設置和運行測試,請參考 Testing from Eclipse with ADT。若是你用的不是 Eclipse 請參考 Testing from Other IDEs。
若是你要一步一步地學習測試 Activity 。請參考 Activity Testing Tutorial,它指導你針對一個 Activity-oriented 應用建立一個完整的測試方案。
Appendix: UI Testing Notes
接下來這幾節講解測試 Android 應用 UI 的一些要點,特別對在測試過程當中如何處理來自於 UI 線程的動做,觸摸、按鍵事件,解鎖 Home Screen 有很大幫助。
Testing on the UI thread
應用程序的 Activity 運行在應用程序的 UI 線程中。一旦 UI 被初始化,好比在 Activity 的 onCreate() 函數中,以後全部和 UI 的交互都必須在 UI 線程中進行。以正常的方式運行程序,它有權限訪問該線程也沒必要作什麼特別的操做。
當運行一個應用程序的測試的時候就有所不一樣了。經過基於 instrumentation 的類,咱們能夠調用待測應用 UI 相關的函數。而其它的測試類就不容許這麼幹。能夠用 @UIThreadTest 給函數註釋讓整個函數在 UI 線程中運行。注意這樣作將使函數的全部語句都運行在 UI 線程中。那些和 UI 沒有交互的函數不容許這樣作;例如,不能(在 UI 線程中)調用 Instrumentation.waitForIdleSync() 函數。
若是想讓測試函數的一部分語句子在 UI 線程中運行,就要建立一個 Runnable 匿名類,而後把想(在 UI 線程中)運行的語句放在 run() 函數中,並把該類的實例做爲參數傳給 appActivity.runOnUiThread() 函數,這個appActivity 就是待測 Activity 的實例。
例以下面的代碼:實例化一個 Activity 用來測試,爲該 Activity 中顯示的一個 Spinner 請求焦點,而後發送一個按鍵事件給它。要注意的是:不容許waiForIdleSync 函數和 sendKeys 函數運行在 UI 線程中。
private MyActivity mActivity; // MyActivity is the class name of the app under test private Spinner mSpinner; ... protected void setUp() throws Exception { super.setUp(); mInstrumentation = getInstrumentation(); mActivity = getActivity(); // get a references to the app under test /* * Get a reference to the main widget of the app under test, a Spinner */ mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01); ... } public void aTest() { /* * request focus for the Spinner, so that the test can send key events to it * This request must be run on the UI thread. To do this, use the runOnUiThread method * and pass it a Runnable that contains a call to requestFocus on the Spinner. */ mActivity.runOnUiThread(new Runnable() { public void run() { mSpinner.requestFocus(); } }); mInstrumentation.waitForIdleSync(); this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
Turning off touch mode
若是要用測試代碼向設備或者模擬器發送按鍵事件,從而控制它們,那麼必須關掉它們的觸摸模式,不然這些按鍵事件就會被忽略掉。
在調用 getActivity() 啓動 Activity 以前調用 ActivityInstrumentationTestCase2.setActivityTouchMode(false) 就能夠關掉觸摸模式。必須在一個非 UI 線程中運行的函數中調用該函數。所以,你不能在一個有 @UIThread 註釋的函數中調用這個觸摸模式函數,而應該在 setUP() 函數中調用。
Unlocking the emulator or device
你會發現,若是模擬器或者設備的 home sceen 處於鍵盤鎖鎖定狀態 UI 測試就沒法正常工做。這是由於待測應用沒法收到從 sendKeys() 發送的按鍵事件。爲了不這個問題,最好的方法是在模擬器或設備啓動後就關閉 home screen 的鍵盤鎖。
也能夠顯式地關閉這個鍵盤鎖。要這樣作須要在 manifest 文件中加一個權限,而後在待測應用程序中關閉鍵盤鎖。可是要注意,在發佈這個應用程序以前也必須把這些代碼移出,或者關閉這些代碼。
加這個權限,就是在 <manifest> 節點加一個子節點 <uses-permission android:name=」android.permission.DISABLE_KEYGUARD」>。而後在待測 Activity 的 onCreate()函數中加入下面代碼來關閉鍵盤鎖:
mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); mLock = mKeyGuardManager.newKeyguardLock("activity_classname"); mLock.disableKeyguard();
其中的 activity_classname 就是待測 Activity 的類名。
Trobleshooting UI tests
這一節列出了咱們在 UI 測試中會常常遇到的失敗的(failures) 測試以及緣由:
WrongThreadException
Problem:
對於這個失敗的測試,失敗棧信息中有下面的錯誤信息:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Probable Cause:
若是在一個 UI 線程以外向該 UI 線程發送 UI 事件就會常常發生這種錯誤。這一般是由於你在測試代碼中發送 UI 事件,而又沒有用 @UIThread 註釋也沒有用 runOnUiThead() 函數。就是說測試函數企圖在 UI 線程以外和 UI 作交互。
Sugested Resolution:
在 UI 線程總進行 UI 交互。用有 instrumentation 的 test case 類。詳細信息請參考前面的 Testing on the UI Thread 章節。
java.lang.RuntimeException
Problem:
對於這個失敗的測試,失敗棧信息中有下面的錯誤信息:
java.lang.RuntimeException: This method can not be called from the main application thread
Probable Cause:
若是你的測試函數已經有了 @UiThreadTest 註釋,可是又企圖作一些 UI 線程之外的事情,或者又去調用 runOnUiThread() 函數。
Suggested Resolution:
移除 @UiThreadTest 註釋,不要調用 runOnUiThead() 函數,或者重構你的測試。