Android提供了一系列強大的測試工具,它針對Android的環境,擴展了業內標準的JUnit測試框架。儘管你可使用JUnit測試Android工程,但Android工具容許你爲應用程序的各個方面進行更爲複雜的測試,包括單元層面及框架層面。android
Android測試環境的主要特徵有:正則表達式
能夠訪問Android系統對象。數據庫
Instrumentation框架能夠控制和測試應用程序。編程
Android系統經常使用對象的模擬版本。app
運行單個test或test suite的工具,帶或不帶Instrumentation。框架
支持以Eclipse的ADT插件和命令行方式管理Test和Test工程。ide
這篇文章是對Android測試環境和測試方法的簡要介紹,並假設你已經擁有必定的Android應用程序編程及JUnit測試的經驗。函數
概要工具
Android測試環境的核心是一個Instrumentation框架,在這個框架下,你的測試應用程序能夠精確控制應用程序。使用Instrumentation,你能夠在主程序啓動以前,建立模擬的系統對象,如Context;控制應用程序的多個生命週期;發送UI事件給應用程序;在執行期間檢查程序狀態。Instrumentation框架經過將主程序和測試程序運行在同一個進程來實現這些功能。單元測試
經過在測試工程的manifest文件中添加<instrumentation>元素來指定要測試的應用程序。這個元素的特性指明瞭要測試的應用程序包名,以及告訴Android如何運行測試程序。在Inustrumentation TestRunner章節有更多的細節描述。
下面的圖片概要的描述了Android的測試環境:
在Android中,測試程序也是Android程序,所以,它和被測試程序的書寫方式有不少相同的地方。SDK工具能幫助你同時建立主程序工程及它的測試工程。你能夠經過Eclipse的ADT插件或者命令行來運行Android測試。Eclipse的ADT提供了大量的工具來建立測試用例,運行以及查看結果。
Testing API
Android提供了基於JUnit測試框架的測試API來書寫測試用例和測試程序。另外,Android還提供了強大的Instrumentation框架,容許測試用例訪問程序的狀態及運行時對象。
下面的章節描述了Android中可利用的主要測試API。
JUnit TestCase類
繼承自JUnit的TestCase,不能使用Instrumentation框架。但這些類包含訪問系統對象(如Context)的方法。使用Context,你能夠瀏覽資源,文件,數據庫等等。基類是AndroidTestCase,通常常見的是它的子類,和特定組件關聯。
子類有:
ApplicationTestCase——測試整個應用程序的類。它容許你注入一個模擬的Context到應用程序中,在應用程序啓動以前初始化測試參數,並在應用程序結束以後銷燬以前檢查應用程序。
ProviderTestCase2——測試單個ContentProvider的類。由於它要求使用MockContentResolver,並注入一個IsolatedContext,所以Provider的測試是與OS孤立的。
ServiceTestCase——測試單個Service的類。你能夠注入一個模擬的Context或模擬的Application(或者二者),或者讓Android爲你提供Context和MockApplication。
Instrumentation TestCase類
繼承自JUnit TestCase類,並可使用Instrumentation框架,用於測試Activity。使用Instrumentation,Android能夠向程序發送事件來自動進行UI測試,並能夠精確控制Activity的啓動,監測Activity生命週期的狀態。
基類是InstrumentationTestCase。它的全部子類都能發送按鍵或觸摸事件給UI。子類還能夠注入一個模擬的Intent。
子類有:
ActivityTestCase——Activity測試類的基類。
SingleLaunchActivityTestCase——測試單個Activity的類。它能觸發一次setup()和tearDown(),而不是每一個方法調用時都觸發。若是你的測試方法都是針對同一個Activity的話,那就使用它吧。
SyncBaseInstrumentation——測試Content Provider同步性的類。它使用Instrumentation在啓動測試同步性以前取消已經存在的同步對象。
ActivityUnitTestCase——對單個Activity進行單一測試的類。使用它,你能夠注入模擬的Context或Application,或者二者。它用於對Activity進行單元測試。
不一樣於其它的Instrumentation類,這個測試類不能注入模擬的Intent。
ActivityInstrumentationTestCase2——在正常的系統環境中測試單個Activity的類。你不能注入一個模擬的Context,但你能夠注入一個模擬的Intent。另外,你還能夠在UI線程(應用程序的主線程)運行測試方法,而且能夠給應用程序UI發送按鍵及觸摸事件。
Assert類
Android還繼承了JUnit的Assert類,其中,有兩個子類,MoreAsserts和ViewAsserts:
MoreAsserts類包含更多強大的斷言方法,如assertContainsRegex(String, String),能夠做正則表達式的匹配。
ViewAsserts類包含關於Android View的有用斷言方法,如assertHasScreenCoordinates(View, View, int, int),能夠測試View在可視區域的特定X、Y位置。這些Assert簡化了UI中幾何圖形和對齊方式的測試。
Mock對象類
Android有一些類能夠方便的建立模擬的系統對象,如Application,Context,Content Resolver和Resource。Android還在一些測試類中提供了一些方法來建立模擬的Intent。由於這些模擬的對象比實際對象更容易使用,所以,使用它們能簡化依賴注入。你能夠在android.test和android.test.mock中找到這些類。
它們是:
IsolatedContext——模擬一個Context,這樣應用程序能夠孤立運行。與此同時,還有大量的代碼幫助咱們完成與Context的通訊。這個類在單元測試時頗有用。
RenamingDelegatingContext——當修改默認的文件和數據庫名時,能夠委託大多數的函數到一個存在的、常規的Context上。使用這個類來測試文件和數據庫與正常的系統Context之間的操做。
MockApplication,MockContentResolver,MockContext,MockDialogInterface,MockPackageManager,MockResources——建立模擬的系統對象的類。它們只暴露那些對對象的管理有用的方法。這些方法的默認實現只是拋出異常。你須要繼承這些類並重寫這些方法。
Instrumentation TestRunner
Android提供了自定義的運行測試用例的類,叫作InstrumentationTestRunner。這個類控制應用程序處於測試環境中,在同一個進程中運行測試程序和主程序,而且將測試結果輸出到合適的地方。IntrumentationTestRunner在運行時對整個測試環境的控制能力的關鍵是使用Instrumentation。注意,若是你的測試類不使用Instrumentation的話,你也可使用這個TestRunner。
當你運行一個測試程序時,首先會運行一個系統工具叫作Activity Manager。Activity Manager使用Instrumentation框架來啓動和控制TestRunner,這個TestRunner反過來又使用Intrumentation來關閉任何主程序的實例,而後啓動測試程序及主程序(同一個進程中)。這就能確保測試程序與主程序間的直接交互。
在測試環境中工做
對Android程序的測試都包含在一個測試程序裏,它自己也是一個Android應用程序。測試程序以單獨的Android工程存在,與正常的Android程序有着相同的文件和文件夾。測試工程經過在manifest文件中指定要測試的應用程序。
每一個測試程序包含一個或多個針對特定類型組件的測試用例。測試用例裏定義了測試應用程序某些部分的測試方法。當你運行測試程序,Android會在相同進程里加載主程序,而後觸發每一個測試用例裏的測試方法。
測試工程
爲了開始對一個Android程序測試,你須要使用Android工具建立一個測試工程。工具會建立工程文件夾、文件和所需的子文件夾。工具還會建立一個manifest文件,指定被測試的應用程序。
測試用例
一個測試程序包含一個或多個測試用例,它們都繼承自Android TestCase類。選擇一個測試用例類取決於你要測試的Android組件的類型以及你要作什麼樣的測試。一個測試程序能夠測試不一樣的組件,但每一個測試用例類設計時只能測試單一類型的組件。
一些Android組件有多個關聯的測試用例類。在這種狀況下,在可選擇的類間,你須要判斷你要進行的測試類型。例如,對於Activity來講,你有兩個選擇,ActivityInstrumentationTestCase2和ActivityUnitTestCase。
ActivityInstrumentationTestCase2設計用於進行一些功能性的測試,所以,它在一個正常的系統環境中測試Activity。你能夠注入模擬的Intent,但不能是模擬的Context。通常來講,你不能模擬Activity間的依賴關係。
相比而言,ActivityUnitTestCase設計用於單元測試,所以,它在一個孤立的系統環境中測試Activity。換句話說,當你使用這個測試類時,Activity不能與其它Activity交互。
做爲一個經驗法則,若是你想測試Activity與Android的交互的話,使用ActivityInstrumentationTestCase2。若是你想對一個Activity作迴歸測試的話,使用ActivityUnitTestCase。
測試方法
每一個測試用例類提供了能夠創建測試環境和控制應用程序的方法。例如,全部的測試用例類都提供了JUnit的setUp()方法來搭建測試環境。另外,你能夠添加方法來定義單獨的測試。當你運行測試程序時,每一個添加的方法都會運行一次。若是你重寫了setUp()方法,它會在每一個方法運行前運行。類似的,tearDown()方法會在每一個方法以後運行。
測試用例類提供了大量的對組件啓動和中止控制的方法。因爲這個緣由,在運行測試以前,你須要明確告訴Android啓動一個組件。例如,你可使用getActivity()來啓動一個Activity。在整個測試用例期間,你只能調用這個方法一次,或者每一個測試方法一次。甚至你能夠在單個測試方法中,調用它的finishing()來銷燬Activity,而後再調用getActivity()從新啓動一個。
運行測試並查看結果
編譯完測試工程後,你就可使用系統工具Activity Manager來運行測試程序。你給Activity Manager提供了TestRunner的名(通常是InstrumentationTestRunner,在程序中指定);名包括被測試程序的包名和TestRunner的名。Activity Manager加載並啓動你的測試程序,殺死主程序的任何實例,而後在測試程序的同一個進程里加載主程序,而後傳遞測試程序的第一個測試用例。這個時候,TestRunner會接管這些測試用例,運行裏面的每一個測試方法,直到全部的方法運行結束。
若是你使用Eclipse,結果會在JUnit的面板中顯示。若是你使用命令行,將輸出到STDOUT上。
測試什麼?
除了一些功能測試外,這裏還有一些你應該考慮測試的內容:
Activity生命週期事件:你應該測試Activity處理生命週期事件的正確性。例如,一個Activity應該在pause或destroy事件時保存它的狀態。記住一點的是屏幕方向的改變也會引起當前Activity銷燬,所以,你須要測試這種偶然狀況確保不會丟失應用程序狀態。
數據庫操做:你應該確保數據庫操做能正確處理應用程序狀態的變化。使用android.test.mock中的模擬對象。
屏幕大小和分辨率:在發佈程序以前,確保在全部要運行的屏幕大小和分辨率上測試經過。你可使用AVD來測試,或者使用真實的目標設備進行測試。
附加:UI測試
接下來的章節爲應用程序UI的測試提供了一些提示,特別是幫助你在UI線程裏處理動做,觸屏和按鍵事件,和鎖屏。
UI線程中測試
Activity運行在程序的UI線程裏。一旦UI初始化後,例如在Activity的onCreate()方法後,全部與UI的交互都必須運行在UI線程裏。當你正常運行程序時,它有權限能夠訪問這個線程,而且不會出現什麼特別的事情。
當你運行測試程序時,這一點發生了變化。在帶有instrumentation的類裏,你能夠觸發方法在UI線程裏運行。其它的測試用例類不容許這麼作。爲了一個完整的測試方法都在UI線程裏運行,你可使用@UIThreadTest來聲明線程。注意,這將會在UI線程裏運行方法裏全部的語句。不與UI交互的方法不容許這麼作;例如,你不能觸發Instrumentation.waitForIdleSync()。
若是讓方法中的一部分代碼運行在UI線程的話,建立一個匿名的Runnable對象,把代碼放到run()方法中,而後把這個對象傳遞給appActivity.runOnUiThread(),在這裏,appActivity就是你要測試的app對象。
例如,下面的代碼實例化了一個要測試的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()中調用。
模擬器或設備的解鎖
你可能已經發現,若是模擬器或設備的鍵盤保護模式使得HOME畫面不可用時,UI測試不能正常工做。這是由於應用程序不能接收sendKeys()的事件。避免這種狀況最好的方式是在啓動模擬器或設備時關閉鍵盤保護模式。
你還能夠顯式地關閉鍵盤保護。這須要在manifest文件中添加一個權限,而後就能在程序中關閉鍵盤保護。注意,你必須在發佈程序以前移除這個,或者在發佈的程序中禁用這個功能。
在<manifest>元素下添加<uses-permission android:name=」androd.permission.DISABLE_KEYGUARD」/>。爲了關閉鍵盤保護,在你測試的Activity的onCreate()方法中添加如下代碼:
mKeyGuardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE);
mLock = mKeyGuardManager.newKeyguardLock("activity_classname");
mLock.disableKeyguard();
這裏,activity_classname是Activity的類名。