Android Testing和Instrumentation
Android提供了一系列強大的測試載入器,它針對Android的環境,擴充了業內標準的JUnit測試架構。儘管你可以使用JUnit測試Android工程,但Android工具允許你為應用程式的各個方面進行更為複雜的測試,包括單元層面及架構層面。
Android測試環境的主要特徵有:
l可以訪問Android系統對象。
lInstrumentation架構可以控制和測試應用程式。
lAndroid系統常用對象的類比版本。
l運行單個test或test
suite的工具,帶或不帶Instrumentation。
l支援以Eclipse的ADT外掛程式和命令列方式管理Test和Test工程。
概要
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架構,允許測試案例訪問程式的狀態及運行時對象。
JUnit TestCase類
繼承自JUnit的TestCase,不能使用Instrumentation架構。但這些類包含訪問系統對象(如Context)的方法。使用Context,你可以瀏覽資源,檔案,資料庫等等。基類是AndroidTestCase,一般常見的是它的子類,和特定組件關聯。
子類有:
l ApplicationTestCase——測試整個應用程式的類。它允許你注入一個類比的Context到應用程式中,在應用程式啟動之前初始化測試參數,並在應用程式結束之後銷毀之前檢查應用程式。
l ProviderTestCase2——測試單個ContentProvider的類。因為它要求使用MockContentResolver,並注入一個IsolatedContext,因此Provider的測試是與OS孤立的。
l ServiceTestCase——測試單個Service的類。你可以注入一個類比的Context或類比的Application(或者兩者),或者讓Android為你提供Context和MockApplication。
Instrumentation TestCase類
繼承自JUnit TestCase類,並可以使用Instrumentation架構,用於測試Activity。使用Instrumentation,Android可以向程式發送事件來自動進行UI測試,並可以精確控制Activity的啟動,監測Activity生命週期的狀態。
基類是InstrumentationTestCase。它的所有子類都能發送按鍵或觸摸事件給UI。子類還可以注入一個類比的Intent。
子類有:
l ActivityTestCase——Activity測試類別的基類。
l SingleLaunchActivityTestCase——測試單個Activity的類。它能觸發一次setup()和tearDown(),而不是每個方法調用時都觸發。如果你的測試方法都是針對同一個Activity的話,那就使用它吧。
l SyncBaseInstrumentation——測試Content Provider同步性的類。它使用Instrumentation在啟動測試同步性之前取消已經存在的同步對象。
l ActivityUnitTestCase——對單個Activity進行單一測試的類。使用它,你可以注入類比的Context或Application,或者兩者。它用於對Activity進行單元測試。
不同於其它的Instrumentation類,這個測試類別不能注入類比的Intent。
l ActivityInstrumentationTestCase2——在正常的系統內容中測試單個Activity的類。你不能注入一個類比的Context,但你可以注入一個類比的Intent。另外,你還可以在UI線程(應用程式的主線程)運行測試方法,並且可以給應用程式UI發送按鍵及觸摸事件。
Assert類
Android還繼承了JUnit的Assert類,其中,有兩個子類,MoreAsserts和ViewAsserts:
MoreAsserts類包含更多強大的斷言方法,如assertContainsRegex(String, String),可以作Regex的匹配。
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線程裡運行:
Java代碼:
- private MyActivity mActivity;
- private Spinner mSpinner;
- ...
- protected void setUp() throws Exception {
- super.setUp();
- mInstrumentation = getInstrumentation();
- mActivity = getActivity();
- mSpinner = (Spinner) mActivity.findViewById(com.android.demo.myactivity.R.id.Spinner01);
- public void aTest() {
- 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()方法中添加以下代碼:
Java代碼:
- mKeyGuardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE);
- mLock = mKeyGuardManager.newKeyguardLock("activity_classname");
- mLock.disableKeyguard();
複製代碼