原文地址:https://developer.android.com/tools/testing/activity_testing.html
Activity測試非常依賴於android instrumentation架構,不像其它組件,Activity有比較複雜的基於回調方法的生命週期,這些方法不能被直接調用,除了instrumentation。同時,程式中向UI發送事件的唯一途徑就是instrumentation。
本文檔描述如何用 instrumentation和其它測試組件來測試Activity。文檔假設你已經閱讀了Testing Fundamentals,它對android 測試和 instrumentation做了基本介紹。
Activity測試API
Activity測試API的基類是InstrumentationTestCase,它為你使用的test case 子類提供了instrumentation。
對於Activity測試,該基類提供了如下功能:
- 生命週期控制:通過instrumentation,你可以使用test case類提供的方法來啟動、暫停、銷毀被測試的Activity。
- 注入依賴:instrumentation讓你可以建立類比的例如Context、Application的系統組件,並且用它們來啟動被測試Activity。這可以協助你控制測試環境同時與實際系統隔離開。你也可以建立自訂的Intent並且用它來啟動Activity。
- UI互動:你可以用instrumentation直接向被測試Activity的UI發送擊鍵或者觸控時間。
Activity測試類別通過繼承TestCase和Assert類同樣支援JUnit架構。
兩個主要用來測試的子類是ActivityInstrumentationTestCase2和ActivityUnitTestCase,如果需要測試一個Activity從非standard模式啟動,你可以使用SingleLaunchActivityTestCase。
ActivityInstrumentationTestCase2
ActivityInstrumentationTestCase2 test case類使用正常的系統內容來對應用中一個或多個Activity做功能測試。它使用標準的系統Context,運行正常的被測試應用中的Activity。它允許你向被測試Activity發送類比的Intent,這樣你可以用它來測試可以處理多種類型Intent的Activity、期望接收Intent中特定類型資料的Activity或者兩種都有的Activity。注意:它並不支援類比的Context和Application,所以你無法將測試和正常系統隔離開。
ActivityUnitTestCase
ActivityUnitTestCase在隔離情況下測試一個單獨的Activity。在啟動Activity前,你必須注入一個類比的Context或者Application,或者兩個都有。你可以用它在隔離狀況下運行Activity測試,執行不與android互動的單元測試。你無法向被測試的Activity發送類比的Intent,但是你可以調用Activity.startActivity(Intent)然後查看收到的參數。
SingleLaunchActivityTestCase
SingleLaunchActivityTestCase類是用來方便在測試與測試之間不會改變的環境中對單個Activity進行測試的類,它只會調用setUp()和tearDown()一次,而不是調用每個測試方法時都調用,它也不允許注入任何類比對象。
這個test case 類在測試一個Activity以非standard模式啟動時非常有用,它確保測試環境在測試與測試間沒有被重設,你可以用來測試Activity是否能夠正確地處理多次調用。
類比對象以及Activity測試
這裡包含關於如何使用android.test.mock包中的類比對象進行Activity測試的內容。
類比對象MockApplication只有在你用ActivityUnitTestCase
test case類進行Activity測試時才可以使用。預設情況下ActivityUnitTestCase建立一個隱藏的MockApplication對象作為測試的Application,你也可以用setApplication()注入自己的對象。
Activity測試斷言
ViewAsserts為view 定義斷言。你可以用它來檢驗view的對準和位置,也可以用來查看ViewGroup的狀態。
測試什麼
- 輸入響應:測試Activity對在EditText中輸入資料後是否能夠準確響應。將擊鍵事件發送給Activity,然後用
findViewById(int)來檢查VIew的狀態。你可以驗證發送一串合法的擊鍵事件時可以啟用“OK”按鈕,而輸入一串非法的擊鍵事件時“OK”按鈕不可用。你同樣可以測試Activity在接收到非法字元時在View中顯示錯誤資訊。
- 生命週期事件:檢查你的應用中的每個Activity正確地處理生命週期事件。通常情況下,生命週期事件都是能觸發類似onCreate()或者onClick()等回調方法的action,它們來自於系統或者使用者。例如,一個Activity在收到pause或者destory事件時應該儲存現有狀態。記住螢幕方向的改變會導致當前Activity的destory,所以你應該測試裝置的轉動不會導致丟失應用的狀態。
- Intent:測試每個Activity都能正確地處理在manifest檔案中intent filter中列出來的intent。你可以使用
ActivityInstrumentationTestCase2發送類比的Intent給activity。
- 運行時參數改變:測試每個Activity是否能夠正確處理在應用運行過程中可能發生的配置改變。這些包括螢幕方向的改變,當前語言的改變等,如何處理這些改變在Handling Runtime Changes中有說明。
- 螢幕尺寸和解析度:在你發布應用前,確保你你在期望啟動並執行螢幕尺寸和大小上進行測試。你可以在多種螢幕尺寸和解析度的AVD上測試,或者你可以直接在目標機器上測試,更多資訊參見Supporting Multiple Screens。
下一步
要學習如何在eclipse中建立和運行測試,請參閱Testing from Eclipse with ADT,如果你沒有在使用eclipse,參見Testing from Other IDEs。
如果你需要手把手的測試教程,參見Activity Testing Tutorial。
附錄:UI測試注意事項
下面的內容是andorid應用UI測試中需要注意的事項,主要是協助你處理測試過程中在UI線程中執行的事件,觸控和擊鍵事件以及主畫面解鎖等。
在UI線程測試
應用中的Activity都在UI線程中執行,當UI執行個體化後,所有與UI的互動都必須在UI線程中執行。在正常啟動應用時,你就在操作UI線程,沒有什麼特別的。
當你對應用執行測試時就不太一樣,通過instrumentation-based的類,你可以調用被測試應用的操作UI的方法,其它的測試類別是不允許的。要在UI線程中執行一個完整的測試方法,你可以給線程加上註解@UIThreadTest,注意這樣方法的所有語句都會在UI線程中執行,不與UI互動的方法是不允許的,例如你不能調用Instrumentation.waitForIdleSync()。
要在UI線程中執行測試方法的一部分,可以建立一個Runnable的匿名類,將你需要的語句放進run()方法中,然後執行個體化該類並作為參數傳入appActivity.runOnUiThread()方法,其中appActivity是你測試的應用的執行個體。
例如,下面的代碼執行個體化一個測試的Activity,為其中顯示的一個Spinner取得焦點,然後給它發送一個擊鍵事件,注意waitForIdleSync和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);
關閉觸控模式
要在測試中用你發送的事件控制模擬器或者裝置,你必須關閉觸控模式,否則擊鍵事件都會被忽略。
要關閉觸控模式,在調用getActivity()啟動activity前調用ActivityInstrumentationTestCase2.setActivityTouchMode(false),你必須在一個非UI線程的測試方法中調用該方法,所以,你無法在一個註解了@UIThread的測試方法中關閉觸控模式,你應該在setup()中調用它。
解鎖模擬器或者裝置
在主畫面由於鎖屏設定進入鎖定狀態時UI測試是不起作用的,這是由於被測試應用收不到由sendKeys()發送過來的事件,最好的解決辦法是啟動你的模擬器或者裝置後禁用主畫面鎖屏。
你可以顯式地禁用螢幕鎖屏,需要在manifest檔案中添加一個許可權並且在被測試應用中關閉鎖屏,注意,你要麼在發布應用時刪除該代碼,要麼在發布的應用中用代碼關閉該功能。
在manifest檔案中添加<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>作為<manifest>標籤的子項目,在你要測試的Activity的onCreate()中添加下面的代碼來實現禁用鎖屏:
mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); mLock = mKeyGuardManager.newKeyguardLock("activity_classname"); mLock.disableKeyguard();
其中activity_classname是Activity的類名。
UI測試常見問題
下面列出在UI測試中經常會出現的失敗以及其原因:
WrongThreadException:
問題:
失敗跟蹤資訊包含如下錯誤資訊:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
可能原因:
這個錯誤來源於你試圖從UI線程以外的線程發送UI事件給UI線程。通常發生於從測試包中發送UI事件但是沒有用@UIThread註解或者runOnUiThread()方法。
建議:
在UI線程中執行互動,使用提供instrumentation的測試類別。可以參見前面的Testing on the UI Thread。
java.lang.RuntimeException:
問題:
失敗跟蹤資訊包含如下錯誤資訊:
java.lang.RuntimeException: This method can not be called from the main application thread
可能原因:
這個錯誤通常是由於你的測試方法用@UiThreadTest註解,但是試圖做UI線程之外的事情或者調用runOnUiThread()。
建議:
刪除@UiThreadTest註解,刪除runOnUiThread()方法,或者重構你的測試。