Android 測試框架是其開發環境的一部分,它提供了一個測試架構和一個強大的工具集來幫助咱們對程序的方方面面進行測試,包括從單元測試到框架測試各個層次。html
該測試框架有三個關鍵點:java
這個文檔描述了 Android 測試框架的基本原來,包括測試結構、用於測試的 API、用於運行測試和查看測試結果的工具。這裏假定你已經掌握了 Android 開發和 JUnit測試的基本知識。android
下面是測試框架圖:正則表達式
Test Structure數據庫
Android 的 build 和測試工具假定測試 projects 被組織成一個標準的測試結構、測試用例類、測試包和測試 projects。架構
Android 測試是基於 JUnit的。一般,一個JUnit test 就是一個函數,這個函數用於測試待測項目的部分功能。把這些測試函數所屬的類就叫測試用例(test cases)或者測試套件(test suites)。每一個測試都是針對待測項目中一個單獨模塊的獨立測試。每一個測試用例類都是相關函數的一個容器,另外它也提供一些輔助函數。app
在 JUnit 中,咱們能夠把一個或者多個測試源代碼文件構建成一個 class文件。一樣,咱們也能夠用 SDK 構建工具把一個或者多個測試源代碼文件構建成 Android測試包中的一個 class 文件。在 JUnit 中咱們用一個 test runner 來執行 test class。一樣,在 Android 中咱們用測試工具加載測試包和待測程序,而後由測試工具調用 Andoid 特定的 test runner。框架
Test Projectsdom
測試(代碼),就像 Android 應用程序(代碼)同樣被弄成 project。eclipse
一個 test project 就是一個文件夾或者是一個 Eclipse project,咱們建立的源代碼文件夾、manifest 文件和這個測試的其它文件都包含其中。Android SDK 提供了一些工具,經過這些工具咱們能夠建立、更新 test project,這些工具既有 Eclipse ADT使用方式,也有命令行的使用用方式。這些工具能夠建立 test project的源代碼文件夾、資源文件夾和 manifest 文件。經過命令行的方式咱們還能夠建立所需的 Ant build 文件。
一般,咱們都應該使用 Android tools 來建立一個 test project。在衆多好處中,這些工具尤爲能夠:
儘管咱們能夠在文件系統的任何地方建立一個 test project,可是最好的作法是把 test project 添加到待測 project 中,這樣它的根目錄 tests/ 就和 待測 project 的 src/ 處於同一目錄層次。這有利於咱們快速查找一個應用的相關測試。例如,若是你的待測 project 的根目錄是 MyProject,那麼你應當用下面的目錄結構:
MyProject/
AndroidManifest.xml
res/
...(待測 project 的資源文件)
src/
...(待測 project 的源代碼)
tests/
AndroidManifest.xml
res/
...(test project 的資源文件)
src/
...(test project 的源代碼)
The Testing API
Android 測試 API 基於 JUnit API 擴展了一個 instrumetation 框架和一些 Android 特有的測試類。
JUnit
咱們能夠用 JUnit TestCase 對一個沒有調用 Android API 的類進行單元測試。咱們能夠經過 AndroidTestCase 來進行測試依賴於 Android 的對象,該類是 TestCase 的子類。除了 JUnit 框架已有的功能外,AndroidTestCase 也提供了 Android 特有的 setup、teardown 方法和一些輔助函數。
用 JUnit Assert 函數來顯示測試結果。該函數對測試的指望結果和實際結果進行比較,若是比較失敗它就會拋出一個異常。Android 也提供了一個斷言類,它繼承了Assert 現有的斷言,而且針對 UI 提供來其它的斷言類,詳情請參閱 Assertion classes 部分。
更深刻的學習 JUnit ,請參閱 junit.org 主頁的文檔。記住,Android testing API 只支持 JUnit 3 的編碼風格,不支持 JUnit 4。另外必須使用 Android 的測試運行器 InstrumentationTestRunner 來運行你的測試用例類。在 Running Tests 部分會解釋該測試運行器。
Instrumentation
Android instrumentation 在 Android 系統中是一個控制函數集合或者「hooks」 ,這些鉤子控制一個 Android 組件,使其脫離正常的生命週期。它們也控制着 Android 若是加載應用程序。
正常狀況下,一個 Android 組件運行在受系統控制的生命週期內。例如,一個 Activity 對象, 它的生命週期開始於它被一個 Intent 激活時。它的 onCreate()函數被調用,而後是 onResume()函數。當用戶啓動其它應用時,onPause()函數被調用。若是調用 Activity 的 finish() 函數,那麼 onDestroy() 函數會被調用。Android 框架不會提供接口讓咱們直接調用這些回調函數,可是咱們能夠經過 Instrumentation 作到這一點。
一樣,系統把一個應用中的全部組件都運行在同一進程中。咱們可讓一些組件,好比 content provider,運行在獨立的進程中,可是咱們不能強制一個應用和另一個已經運行的應用運行在同一進程中。
可是,經過 Instrumentation 咱們能夠在測試代碼中調用這些回調方法。這樣咱們就能夠一步一步的遍歷一個組件的生命週期,就好象你在調試組件同樣。下面的代碼片斷演示瞭如何用 Instrumentation 來測試一個 Activity 的存儲動做和恢復狀態動做:
//啓動待測應用的 main activity
mActivity = getActivity();
//獲取這個 activity 的主要 UI 組件,就是一個 Spinner
mSpinner = (Spinner)myActivity.findViewById(com.android.example.spinner.R.id.Spinner01);
//把一個已知的位置設置給這個 Spinner
myActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
//中止 Activity -應該在 onDestroy() 函數內保存 Spinner 的狀態。
mActivity.finish();
//重啓 Activity - 應當在 onResume() 函數中恢復 Spinner 以前的狀態
mActivity = getActivity();
//獲取 Spinner 當前的位置
int currentPosition = mActivity.getSpinnerPosition();
//斷言當前位置和以前的位置同樣。
assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
這裏所用的關鍵函數是 getActivity(),它是屬於 instrumentation API 。待測 Activity 直到調用這個函數才啓動。咱們能夠先設置一些高級測試配置,而後再啓動Activity。
一樣,instrumentation 能夠把測試程序和待測程序加載到一個進程中。由於應用組件和它的測試程序在一個進程中,那麼測試程序就能夠調用組件的函數,並能夠修改、檢查組件的屬性了。
Test case classes
Android 提供了一些繼承 TestCase 和 Assert 的 test case 類,這些類另外還提供了 Android 特有的 setup、teardown 和 輔助方法。
AndroidTestCase
AndroidTestCase 是一個有用的通用 test case 類,尤爲是你剛接觸 Android 測試時。它繼承自 TestCase 和 Assert,它提供 JUnit 標準的 setUP() 和 tearDown() 函數,一樣也提供了 JUnit 的 斷言函數。另外它還提供了一些測試權限的函數,還有一個經過清理某些類的引用來防止內存泄露的函數。
Component-specific test cases
Android 測試框架的一個關鍵特性就是它有一些針對特定組件的 test case 類。這些對組件的特殊測試須要一些設置、卸載資源(fixture)的函數和一些控制組件生命週期的函數。這些類也提供了設置模擬對象的函數。在 component-specific testing 章節會討論這些類:
Android 沒有爲 BroadcastReceiver 單獨提供 test case 類。而是經過向(註冊它的)組件發送 Intent 對象來驗證 BroadcastReceiver 可否正確響應。
ApplicationTestCase
咱們用 ApplicationTestCase test case 類來測試 Application 對象的 setup 和 tearDown。這些對象維護着應用程序的全局信息,應用程序包中的全部組件均可以引用這些信息。該 test case 類用於驗證 manifest 文件中的 <application> 標籤是否正確設置。可是要注意,你不能用該 test case 測試應用程序包中的組件。
InstrumentationTestCase
若是要在一個 test case 類中用 instrumentation 中的函數,必須用 InstrumentationTestCAse 或其子類。Activity test case 繼承自該類,並提供了一些其它的函數以幫助進行 Activity 測試。
Assertion classes
由於 Android 的 test case 類繼承自 JUnit,因此咱們能夠用斷言函數來顯示測試結果。斷言函數對一個測試返回的實際結果和一個指望結果進行比較,若是比較失敗就跑出一個 AssertionException 。用斷言遠比用 log 方便,而且性能更好。
除了 JUnit Assert 類提供的函數以外,測試 API 還提供了 MoreAsserts 和 ViewAsserts 兩個類:
Mock object classes
爲了方便在測試中使用依賴注入,Android 提供了一些類來建立系統模擬對象,好比 Context 對象、ContentProvider 對象、ContentResolver 對象和 Serviec 對象。一些 test case 類還提供了虛擬 Intent 對象。 咱們用這些對象既能夠將測試和系統其它部分隔離開來,又能夠在測試中方便地使用依賴注入。這些類都在 android.test 包和 android.test.mock 包中。
模擬對象經過屏蔽或者覆寫常規操做來把測試和正在運行的系統隔離開來。例如,MockContentResolver 用本身獨立於系統的本地框架代替標準的 Resolver 框架。MockContentResolver 也屏蔽了 notifyChange(Uri,ContentObserver, boolean) 函數,因此測試環境外的 observer 對象就不會不當心被觸發了。
模擬對象類也提供了常規類的子類以方便使用依賴注入,這些模擬類裏面的函數沒有實際的功能,除非你覆寫它。例如,MockResource 類是 Resources 的子類,當調用這個模擬類裏的全部函數時都只是拋出一個異常。要想使用它,咱們只用覆寫要用的函數便可。
下面是 Android 提供的虛擬對象類:
Simple mock object classes
MockApplication,MockContext,MockContentProvider,MockCursor,MockDialogInterface,MockPackageManager,和 MockResources 提供了一個簡單而又實用的模擬策略。他們是系統中相應類的屏蔽版本(就是空實現),它們中的全部函數都只是拋出 UnsupportedOperationException 異常而已。使用的時候,咱們只需覆寫須要提供信息的函數來模擬依賴。
注意: MockContentProvider 和 MockCursor 是 API 8 中新提供的。
Resolver mock objects
MockConentResolver 經過屏蔽系統的常規 resolver 框架爲 content provider 提供一個隔離的測試。MockContentResolver 使用本身的內部表,而不是用一個給定的 authority 在系統中找一個對應的 content provider。咱們必須用 addProvider(String,ContentProvider) 函數顯式地把 provider 加到這個表中。
有了它,咱們能夠把一個模擬的 content provider 和一個 authority 關聯起來。咱們能夠建立一個真實的 provider 而只用它裏面的測試數據。咱們甚至能夠把一個 authority 相應的 provider 設置爲 null。事實上,MockContentResolver 對象把咱們的測試和包含真實數據的 provider 隔離開來。咱們既能夠對 provider 進行操做,又能夠避免測試影響到真實的數據。
Context for testing
Android 提供了兩個對測試頗有用的 Context 類:
該類爲給數據操做設置一個隔離區域提供了一種快速途徑,並保留了 Context 的其它常規功能。
Running Tests
Test case 是經過一個 test runner class 運行的,這個 test runner class 對每一個測試進行加載、set up 、運行、tear down。test runner 還必須裝載上 instrumentation ,以便啓動應用程序的系統工具能夠控制 test package 如何加載 test case 和待測應用。咱們能夠在 test package 的 manifest 文件中設置一個值,來告訴 Android 平臺用哪一個裝備了 instrumentation 的 test runner。
InstrumentationTestRunner 是 Android 主要的 test runner class。它繼承自 JUnit test runner 框架,而且已經裝備了 instrumentation。它能夠運行Android 提供的全部 test case 類,而且支持全部的測試類型。
咱們能夠在 test package 的 manifest 文件的 <instrumentation> 標籤指定 InstrumentationTestRunner 或其子類。它不不一樣 Android 的其它代碼,它屬於共享庫 android.test.runner。要想把包含進項目,咱們必須在 <uses-library> 標籤指定它。我沒必要手動去設置這些標籤。Eclipse 的 ADT 和 android 命令行工具均可以自動構建它們,並加入 test package 的 manifest 文件。
注意:若是你不想用 InstrumentationTestRunner ,你必須修改 <instrumentation> 標籤來指定你想用的類。
要運行 InstrumentationTestRunner,咱們要經過 Android tools 來調用系統的內部類,而後內部類來運行它。當咱們在 帶 ADT 的 Eclipse 中運行測試時,這些類會自動調用。若是咱們經過命令行運行測試,咱們經過 Android Debug Bridge(adb) 來調用這些類。
這些系統內部類負責加載並啓動 test package,首先殺死全部運行待測應用實例的全部進程,而後加載一個新的待測應用的實例。接着就把控制權交給 InstrumentationTestRunner,由 InstrumentationTestRunner 來運行 test package 中的全部 test case。在帶有 ADT 的 Eclipse 中經過設置來決定運行哪些 test cass 或者 方法,或者在命令行中使用標記來實現這一點。
既不是系統內部類啓動待測應用也不是 InstrumentationTestRunner 啓動。而是 test case 直接啓動。它既能夠調用待測應用中的函數,又能夠調用本身的函數,這些函數(本身的函數)能夠觸發待測項目的生命週期事件。待測應用徹底在 test case 的控制之下,這樣 test case 就能夠在運行測試以前設置測試環境(the test fixturre)。這在以前的代碼片斷中已經演示了,該代碼片斷是測試在一個 Activity 中顯示一個 Spinner 控件。
想要了解更多關於運行測試的問題,請參閱 Testing from Eclipse with ADT 或者 Testing from Other IDEs。
Seeing Test Results
Android 測試框架把測試結果返回給了啓動該測試的工具。若是你在帶有 ADT 的 Eclipse 中運行測試,那麼測試機過就會顯示在一個新開的 JUnit 視圖托盤中。若是是經過命令行運行測試,那麼測試結果會在 STDOUT 中顯示。不論是哪一種環境,你均可以看到一個測試總覽,它包括每一個 test case 以及其中運行函數的名字。咱們也能夠看到全部發生的失敗斷言。這些結果都會標註出失敗斷言發生在測試代碼的哪一行。失敗的斷言也會列出指望值和實際值。
測試結果有必定的格式,這與你用的 IDE 有關。Testing from Eclipse with ADT 中描述了使用帶有 ADT 的 Eclipse 測試結果的格式。Testing from Other IDEs 中描述了使用命令行測試結果的格式。
monkey and monkeyrunner
SDK 針對應用程序的功能測試提供了兩個工具:
Working With Package names
在測試環境中,咱們既要用到 Android 應用程序包名,有要用到 Java 包標識符。雖然它們格式相同,但實質上他們表明不一樣的東西。要正確設置測試就要區分它們的不一樣點。
Android 應用程序包名是一個 apk 文件在系統中惟一的名字,經過 manifest 文件中 <manifest> 標籤的 "android:package" 屬性來設置它。測試程序的 Android 應用程序包名必須和待測程序的不一樣。默認狀況下, Android tools 把待測程序的包名加上一個 ".test" 作爲測試程序的包名。
測試程序也會用一個 Android 應用程序包名來指定它的測試目標,這是經過測試程序 manifest 文件中的 <instrutmentation> 標籤的 "android:targetPackage" 屬性來設定的。
Java 包標識符適用於源代碼文件。這包名反應了源代碼文件的路徑。它影響這類之間以及成員之間的可見性。
建立測試工程的 Android 工具會爲咱們設置一個 Android 測試包名。根據咱們的輸入,該工具會設置一個測試包名和測試目標的包名。要想讓這些工具正常運行,(待測)工程必須已經存在。
默認狀況下,這些工具會把測試類的 Java 包標識符和 Android 包名設置成同樣的。若是咱們想經過給它們可性來暴露待測項目中的一些程序出來,你可能須要對其作些修改。若是真要這麼幹,記得只改動 Java 包標識符,不要改 Android 包名,而且只改動 test case 的源文件。不要修改測試包中產生的 R.java 類的 Java 包標識符,由於它會與待測項目中 R.java 類衝突。不要把測試包的 Android 包名改爲和待測項目的同樣,由於這樣他們名字在系統中就不是惟一的了。
What to Test
What to Test 章節介紹了咱們應該測試 Android 應用的哪些關鍵功能,哪些狀況可能會影響到這些功能。
不少單元測試都是針對 Android 組件的。Activity Tesing,Content Provider Tesing 和 Service Testing 這些章節中每一個裏面都有一個 「What To Test」 部分列出須要測試的範圍。
若是能夠,最好在真機運行這些測試。不然,也能夠根據要測設備的硬件、屏幕、版本,用 Android Emulator 配置一個虛擬設配來測試。
Next Steps
在 Eclipse 中如何設置運行測試,請參考 Tesing from Eclipse with ADT,若是不是用 Eclipse 請參考 Testing from Other IDEs。
若是要一步一步學 Android 測試,請參考 Activity Testing Tuorial。