標簽(空格分隔): Android
單元測試的好處:
Martin Fowler在《重構》里面還解釋了為什么單元測試可以節(jié)省時間共苛,大意是我們寫程序的時候拙友,其實大部分時間不是花在寫代碼上面,而是花在debug上面少辣,是花在找出問題到底出在哪上面颠黎,而單元測試可以最快的發(fā)現你的新代碼哪里不work,這樣你就可以很快的定位到問題所在待德,然后給以及時的解決,這也可以在很大程度上防止regression
歷史測試基礎##
Android 測試框架(Android Testing Framework)為 Android 開發(fā)環(huán)境的一個組成部分枫夺,可以用來測試 Android 的各個方面将宪,從單元測試到框架測試到 UI 測試等。
- Android 測試框架基于 JUnit橡庞,因此可以直接使用 JUnit 來測試一些與 Android 平臺不是很相關的類较坛,或者使用Android 的JUint 擴展來測試 Android 組件,如果你剛開始接觸 Android測試扒最,可以先從AndroidTestCase 寫一些通用的測試用例丑勤,然后再寫較復雜的測試用例。
- Android JUint 擴展提供了對 Android 特定組件(如
Activity吧趣,Service)的測試支持法竞,這些擴展類提供了一些輔助方法來幫助創(chuàng)建測試使用的“樁”類或方法耙厚。 -
SDK 也提供了一個 moneyrunner (一個 python 應用)可以模擬用戶按鍵事件來測試 UI。
為什么android unit testing不好做##
我們知道安卓的app需要運行在delvik上面岔霸,我們開發(fā)Android app是在JVM上面薛躬,在開發(fā)之前我們需要下載各個API-level的SDK的,下載的每個SDK都有一個android.jar的包呆细,這些可以在你的android_sdk_home/platforms/下面看到型宝。當我們開發(fā)一個項目的時候,我們需要指定一個API-level絮爷,其實就是將對應的android.jar 加到這個項目的build path里面去趴酣。這樣我們的項目就可以編譯打包了。然而現在的問題是略水,我們的代碼必須運行在emulator或者是device上面价卤,說白了劝萤,就是我們的IDE和SDK只提供了開發(fā)和編譯一個項目的環(huán)境渊涝,并沒有提供運行這個項目的環(huán)境,原因是因為android.jar里面的class實現是不完整的床嫌,它們只是一些stub跨释,如果你打開android.jar下面的代碼去看看,你會發(fā)現所有的方法都只有一行實現:
throw RuntimeException("stub!!”);
而運行unit test厌处,說白了還是個運行的過程鳖谈,所以如果你的unit test代碼里面有android相關的代碼的話,那運行的時候將會拋出RuntimeException("stub!!”)阔涉。為了解決這個問題缆娃,現在業(yè)界提出了很多不同的程序架構,比如MVP瑰排、MVVM等等贯要,這些架構的優(yōu)勢之一,就是將其中一層抽出來椭住,變成pure Java實現崇渗,這樣做unit testing就不會遇到上面這個問題了,因為其中沒有android相關的代碼京郑。
好奇的童鞋可能會問了宅广,既然android.jar的實現是不完整的,那為什么我們可以編譯這個項目呢些举?那是因為編譯代碼的過程并沒有真正的運行這些代碼跟狱,它只會檢查你的接口有沒有定義,以及其他的一些語法是不是正確户魏。舉個簡單的例子:
public class Test {
public static void main(String[] argv) {
testMethod();
}
public static void testMethod() {
throw RuntimeException("stub!!”);
}
}
上面的代碼你同樣可以編譯通過兽肤,但你運行的時候套腹,就會拋出異常RuntimeException("stub!!”)。當我們的項目運行在emulator或者是device上面的時候资铡,android.jar被替換成了emulator或者是device上面的系統的實現电禀,那上面的實現是真正實現了那些方法的,所以運行起來沒有問題笤休。
話說回來尖飞,MVP、MVVM這些架構模式雖然解決了部分問題店雅,可以測試項目中不含android相關的類的代碼政基,然而一個項目中還是有很大部分是android相關的代碼的,所以上面那種解決方案闹啦,其實是放棄了其中一大塊代碼的unit test沮明。
當然,話說回來窍奋,android還是提供了他自己的testing framework荐健,叫instrumentation,但是這套框架還是繞不開剛剛提到的問題琳袄,他們必須跑在emulator或者是device上面江场。這是個很慢的過程,因為要打包窖逗、dexing址否、上傳到機器、運行起來界面碎紊。佑附。。這個相信大家都有體會仗考,尤其是項目大了以后音同,運行一次甚至需要一兩分鐘,項目小的話至少也要十幾秒或幾十秒痴鳄。以這個速度是沒有辦法做unit test的瘟斜。
那么怎么樣即可以給android相關的代碼做測試,又可以很快的運行這些測試呢痪寻?
Android 的測試框架相關的 API 主要定義在三個包中:
- android.test 用于編寫 Android 測試用例
- android.test.mock 定義了方便測試用的測試“樁”類
- android.test.suitebuilder 運行測試用例的 Test Runner 類 Android 測試 API 是基于
JUnit 擴展而來螺句,并添加了與 Android 平臺相關的測試 API。
JUnit###
你可以直接使用 JUnit 中相關 API 編寫一些和平臺無關的測試用例(基于 TestCase), Android 測試 API 中提供了一個 TestCase 的子類 AndroidTestCase 橡类,可以用來編寫一些 Android 相關的對象的測試用例蛇尚,AndroidTestCase 支持一些和平臺相關的 setup,teardown 以及 setup 方法。
你也可以直接使用 JUnit 的 Assert 方法 顯示測試結果顾画,這些 Assert 方法可以通過比較預期的值和實際的值取劫,如果不同可以排除異常匆笤。Android 測試 API 擴展了一些 Assert 方法用于支持和 Android 平臺相關的比較。
要注意的是谱邪,Android 測試 API 支持 JUnit 3 代碼風格炮捧,而不支持 JUnit 4 代碼風格谅海,也只能使用 InstrumentationTestRunner 來運行測試用例扁藕。
Instrumentation###
在通常情況下(普通的 Android 應用)瑟幕,Android 的 activity框喳,Service 等的生命周期是由 Android 操作系統來控制的。 比如一個 Activity 的生命周期開始于 onCreate (由某個 Intent 激活)蹋艺,然后是 onResume. 可以參見 Android 簡明開發(fā)教程五:Activities谢肾。 應用程序本身無法直接控制這些生命周期狀態(tài)的切換浦马。但使用 Instrumatation API 時你可以直接調用這些方法迅栅。
可以獨立控制 Android 組件(Activity殊校,Service 等)的生命周期,并可以控制 Android 如何調用一個應用读存。也可以支持強制某個應用和另一個已經在運作的應用運行在同一個進程中
Test case 相關類###
Android 提供了多個由 Testcase 或 Assert 派生而來的子類以支持 Android 平臺相關的 setup,teardown 和其它輔助方法为流。
- AndroidTestCase 為一 Android 平臺下通用的測試類,它支持所有 JUnit 的 Assert 方法和標準的
setUp 和 tearDown 方法宪萄,并可以用來測試 Android permission 艺谆。
setUp()測試初始化拜英,Runner在運行任何其它測試方法之前自動執(zhí)行setUp()方法,可以在這里建立測試數據集
tearDown() 會在測試完成後執(zhí)行琅催,多半用於測試資料的移除與資源回收等工作居凶。
- 組件相關的測試類如測試 activity, Content provider ,Service 相關的測試類,Android
沒有提供單獨的用來測試 BroadcastReceiver 的測試類藤抡,而是可以通過發(fā)送 Intent 對象來檢測 Broadcast
Receiver 的反應結果來測試 BroadcastReceiver侠碧。 - ApplicationTestCase 可以用來測試 Application 對象。
- InstrumentationTestCase 如果你要使用 Instrumentation API缠黍,那么你必須使用 InstrumentationTestCase 或其子類弄兜。
建議再開始下面的內容之前先看看這篇文章
1、在AndroidStudio中使用原生的androidUnit測試的具體操作:##
首先因為上面的提到的基礎有點難懂瓷式,所以建議看一下中文官方文檔替饿,還有這篇博客
Android提供了上面的多個測試類,可以允許我們對于單個方法贸典、Activity视卢、Service、Application等多個對象進行測試廊驼,
參考博客
1据过、可以自己新建一個測試目錄惋砂,把測試目錄放到你想要的地方,也可以把單元測試類創(chuàng)建在與Android Studio默認的ApplicationTest類相同的路徑下面绳锅;以前的AndroidStudio版本或者是ADT的話要自己新建西饵,具體新建操作請見博客一博客二博客三;但是最近的AndroidStudio版本已經自動為我們生成用于測試安卓的AndroidTest目錄與java的JUnnit目錄鳞芙,所以可以省去自己動手新建目錄
2罗标、所有的測試方法必須以”test”開頭,這樣Android Studio才能自動的找到所有你想要進行單元測試的方法积蜻。
3闯割、配置gradle:其實這一步配置可以省略,因為Android Studio本身就支持Android單元測試
進入app對應的gradle里竿拆,在android里的defaultConfig加上
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"宙拉;
在dependencies 里加上
testCompile 'junit:junit:4.12'//用于java的單元測試
//以下的兩個用于安卓的測試
androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'
4、UI測試
當測試擁有UI的Activity時丙笋,被測試的Activity在UI線程中運行谢澈。然而,測試程序會在程序自己的進程中御板,單獨的一個線程內運行锥忿。這意味著,我們的測試程序可以獲得UI線程的對象怠肋,但是如果它嘗試改變UI線程對象的值敬鬓,會得到WrongThreadException錯誤。我們可以讓測試類繼承于ActivityInstrumentationTestCase2笙各,這樣的話钉答,可以與Android系統通信,發(fā)送鍵盤輸入及點擊事件到UI中杈抢。就可以測試帶有UI交互的activity了数尿,安全地將Intent注入到Activity。因為要與android系統通信惶楼,所以要有運行環(huán)境右蹦,在虛擬機或者真機!歼捐!
5何陆、單元測試
單元測試一般不適合測試與系統有復雜交互的UI。我們應該使用如同測試UI組件所描述的ActivityInstrumentationTestCase2來對這類UI交互進行測試窥岩。使用單元測試時甲献,我們的測試類應該繼承自ActiviUnitTestCase,ActiviUnitTestCase類提供對于單個Activity進行分離測試的支持颂翼,繼承ActiviUnitTestCase的Activity不會被Android自動啟動晃洒。要單獨啟動Activity慨灭,我們需要顯式的調用startActivity()方法,并傳遞一個Intent來啟動我們的目標Activity球及。不需要用到android運行環(huán)境
6氧骤、功能測試
功能測試包括驗證單個應用中的各個組件是否與使用者期望的那樣(與其它組件)協同工作。比如吃引,我們可以創(chuàng)建一個功能測試驗證在用戶執(zhí)行UI交互時Activity是否正確啟動目標Activity筹陵。
要為Activity創(chuàng)建功能測,我們的測試類應該對ActivityInstrumentationTestCase2進行擴展镊尺。
7朦佩、Applicatipn測試
可以使用ApplicationTestCase,具體請看博客
8庐氮、測試Service
由于Service是在后臺運行的语稠,所以測試Service不能用instrumentation框架,繼承ServiceTestCase的測試類可以對service進行針對性測試弄砍。
ServiceTestcase不會初始化測試環(huán)境直到你調用ServiceTestCase.startService()或者ServiceTestCase.bindService. 這樣的話仙畦,你就可以在Service啟動之前可以設置測試環(huán)境,創(chuàng)建你需要模擬的對象等等音婶。比如你可以配置service的context和application對象慨畸。
setApplication()方法和setContext(Context)方法允許你在Service啟動之前設置模擬的Context 和模擬的Application.關于這些模擬的對象。具體請看博客
2衣式、使用谷歌的測試框架:Testing Support Library包括ndroidJUnitRunner(JUnit 4)寸士、Espresso、UI Automator###
配置如下:
必須的
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
不是必須的瞳收,根據需求選擇三者中的一種或者全選
dependencies {
androidTestCompile 'com.android.support.test:runner:0.4'
// Set this dependency to use JUnit 4 rules
androidTestCompile 'com.android.support.test:rules:0.4'
// Set this dependency to build and run Espresso tests
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
// Set this dependency to build and run UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}
為什么Android應用需要測試##見博客
Google+ 團隊的 Android UI 測試##
Android 測試主要分為3個類型:
- 單元測試(Unit Test)
區(qū)分UI代碼和功能代碼在Android開發(fā)中尤其困難碉京。因為有時Activity既有Controller的功能厢汹,又有View的功能螟深。Robolectric是一個很優(yōu)秀的Android測試框架,它提供了一個Android框架的stub烫葬,這樣測試運行時實際上是在JVM上運行界弧,而不是在Android平臺
(比如Robotium和Instrumentation都是在Android平臺運行測試)
,從而提高了速度搭综。
- 封閉UI測試 (Hermetic UI Test)
這個測試方法使得測試不需要外部依賴和網絡請求垢箕。這樣做的主要目的是提高測試速度,減少測試時的外部影響兑巾,畢竟網絡調用是相對很慢的条获。Espresso可以用來模擬用戶的UI操作。 - Monkey Test
Monkey Test 就好像一只猴子在測試app一樣蒋歌,沒有任何規(guī)律的在你的app上胡按帅掘。計算機運行monkey test的時候委煤,每秒鐘能做出幾千個UI動作(可以配置這個頻率),比如點擊和拖拽修档。所以這個測試可以算是一個壓力測試碧绞,用來檢測ANR。
Android UI 自動化測試框架——UI Automator##
參考文章
谷歌推薦的這是一種較為落后的測試UI的方法吱窝,因為真的要在真系或者虛擬機上跑起來讥邻,這個過程會看到UI的變化過程。而且這個東西并不是真的是自動化院峡,而是你先寫好了界面的交互代碼兴使,然后執(zhí)行的時候用代碼去代替用戶的交互手勢而已。
Espresso——Google引入了新的UI測試框架##
Espresso照激,使得測試Android應用的UI變得更加容易
Espresso測試有個很強大的地方是它在多個測試操作中是線程安全的鲫惶。Espresso會等待當前進程的消息隊列中的UI事件,并且在任何一個測試操作中會等待其中的AsyncTask結束才會執(zhí)行下一個測試实抡。這能夠解決程序中大部分的線程同步問題欠母。參考博客
onView用于定位視圖,perform用于產生事件吆寨,check用于檢測checkpoint赏淌。
具體例子請看:博客
Espresso可以直接跨Activity檢查,不需要知道跳轉關系啄清。不用像以前的原生的androidUnit測試一樣六水,要控制好activity跳轉關系
更多詳細的用法請見谷歌的官方英語文檔更多詳細的例子請見谷歌官方英語文檔
Roboletric——直接在JVM運行的強大的安卓測試框架##
Robolectric在其所提供的測試框架中,完全模擬了Android SDK的jar文件(不會再有惱人的stub異常)辣卒,它使得我們的測試可以運行于JVM之上(速度得到大幅度的提升)掷贾,因此我們可以用它對Android應用進行測試驅動開發(fā)。Roblectric同時實現了Android中對XML的解析荣茫,模擬了View想帅,Layout,以及資源的加載啡莉,它使得Android的環(huán)境對于開發(fā)人員來說更像是一個黑盒港准,從而使開發(fā)人員不用大量使用mock,就可以方便的對資源狀態(tài)和Android相關的代碼進行測試咧欣。
Robolectric使用了javassist在運行時動態(tài)修改Android.jar中類的byte code浅缸,Robolectric會在JVM加載Android.jar包的時候,重寫其中類的方法魄咕。Roblectroic會讓這些方法有返回值(null或是0) 而不是拋出異常 衩椒,或者將這些方法調用轉向Shadow Objects來模擬Android SDK的實現。Shadow Objects是Robolectric在運行時插入到Android.jar包相應的類中的,它們會實際處理方法的調用毛萌,并記錄相應的狀態(tài)梢什,以備在assert的時候進行查詢。如圖所示朝聋。Robolectric提供了大量的Shadow Objects嗡午,覆蓋了測試開發(fā)過程中絕大多數邏輯功能的需要 。
robolectric冀痕,他們的做法是通過實現一套JVM能運行的Android代碼荔睹,然后在unit test運行的時候去截取android相關的代碼調用,然后轉到他們的他們實現的代碼去執(zhí)行這個調用的過程言蛇。舉個例子說明一下僻他,比如android里面有個類叫TextView,他們實現了一個類叫ShadowTextView腊尚。這個類基本上實現了TextView的所有公共接口吨拗,假設你在unit test里面寫到
String text = textView.getText().toString();。在這個unit test運行的時候婿斥,Robolectric會自動判斷你調用了Android相關的代碼textView.getText()劝篷,然后這個調用過程在底層截取了,轉到ShadowTextView的getText實現民宿。而ShadowTextView是真正實現了getText這個方法的娇妓,所以這個過程便可以正常執(zhí)行。
除了實現Android里面的類的現有接口活鹰,Robolectric還做了另外一件事情哈恰,極大地方便了unit testing的工作。那就是他們給每個Shadow類額外增加了很多接口志群,可以讀取對應的Android類的一些狀態(tài)着绷。比如我們知道ImageView有一個方法叫setImageResource(resourceId),然而并沒有一個對應的getter方法叫getImageResourceId()锌云,這樣你是沒有辦法測試這個ImageView是不是顯示了你想要的image荠医。而在Robolectric實現的對應的ShadowImageView里面,則提供了getImageResourceId()這個接口宾抓。你可以用來測試它是不是正確的顯示了你想要的Image.
monkey測試##
測試操作:###
下面的操作一般是在shell端啟動即在真機或者模擬器啟動,所以下面的所有操作的前提是先輸入adb shell 進入終端機石洗。在測試的過程中無論怎么按屏幕或者按鍵也無法停止monkey測試,這能等測試完成=粝浴讲衫!
1、在輸出日志信息的時候,如果只是 輸入命令:monkey 300的話涉兽,日志信息之會出現在命令行招驴,不會保存到文件,如果想保存到文件的話枷畏,可以參考上圖做法别厘。注意monkey后面可以加一些option參數
參數:-v:輸出日內容志的多少,如果是-v-v-v的話拥诡,輸出的內容會更加的詳細
參數:-p 指定的包名:用于選擇那個應用測試触趴。可以同時指定多個應用:-p 指定的包名 -p 指定的包名 -p 指定的包名渴肉。所以要先找到對應的包名冗懦,可以使用pm list packages列出所有的包名。
這里有一點要注意的是:-p只能測試在桌面上有圖標的應用仇祭,即在應用的對應的Main activity的配置文件加了
<category android:name="android.intent.category.LAUNCHER" />
這句話的應用披蕉,所以當要測試一些沒有在桌面有圖標的應用時,只能用下面的-c
參數:-c 指定的包名 用于測試在桌面沒有圖標的應用乌奇。
其實monkey有白名單與黑名單没讲,白名單的作用相當于參數-p,控制哪些應用用于測試礁苗,黑名單則相反食零。但那黑名單與白名單不能同時設置
黑名單:--pkg-blacklist-file PACKAGE_BLACKLIST_FILE
白名單:--pkg-whitelist-file PACKAGE_WHILELIST_FILE
具體使用時,可以先用pm list packages列出所有的包名寂屏,然后再將要用的白名單或者黑名單的報名復制到一個TXT的文本贰谣,然后將這個文本文件一般保存到/data/local/tmp/目錄下,然后就可以大具體的命令:monkey --pkg-blacklist-file
/data/local/tmp/文本文件名(以黑名單為例)
參數:-s 隨機種子數 用隨機種子數來標記這次的測試迁霎,以便下次可以重現這次的測試序列吱抚,復原測試場景。
參數:--throttle 延時毫秒數 可以讓測試時的各個事件之間有延時考廉,以模仿人的操作延時秘豹,人的操作延時一般為200毫秒至300毫秒之間
參數:--randomize-throttle 延時毫秒數 這樣的范圍就變成0至設置的延時毫秒數
更多的參數請上網搜索,除了控制測試的參數之外昌粤,還可以控制測試時的事件既绕。如:touch事件、activity切換是--pptappswitch 百分比加上這個事件參數之后可以控制測試的過程中只打開activity涮坐,但是卻不執(zhí)行touch或者點擊事件 凄贩、點擊事件。
調試操作:###
參數:--ignore-crashes 忽略程序崩潰或任何失控異常袱讹,使monkey還會繼續(xù)執(zhí)行下去
參數:--ignore-timeouts 忽略ANR疲扎,例如在等待輸入等阻塞事件時monkey會跳過這些阻塞事件,繼續(xù)執(zhí)行
參數:-ignore-security-execptions 忽略權限許可錯誤
參數:--ignore-native-crashes 忽略閃退現象
monkey結果分類:###
一般的測試結果顯示如下:首先是
然后是各個事件
這里插入一句各個事件具體的含義解釋如下
然后是
分析異常結果###
ANR有兩個原因,一個是界面5秒鐘沒有響應或者發(fā)送一個廣播在10秒鐘之內沒有被處理
ANR有關如下圖
其中比較重要的是 anr traces他是保存在/data/anr/traces.txt中椒丧,里面有ANR和Crash的原因壹甥;我們要用cd命令進入到里面才能查看
常見異常
測試策略###
先自己預先列一份應用最可能吹西安問題的對癥下藥測試
Monkey腳本編寫與檢查內存泄露###
為什么要自己寫monkey腳本測試呢?
因為自帶的monkey是隨機的測試壶熏,沒有規(guī)律性句柠,雖然可以控制各個事件的比例,但是可能達不到我們需要的邏輯順序與要求棒假。所以我溯职,們可以自己寫一個腳本來固定測試邏輯,比如說打開瀏覽器然后輸入網址淆衷,然后推出瀏覽器缸榄。但那時個人覺得這樣做的話還不如用以前的方法如android原生的單元測試,谷歌的Espresso或者第三方框架Robolectric來測試祝拯。
重要步驟如下:
1甚带、首先除了要測試獲取的包名還要獲取類名,我們可以先用之前的pm list packages列出所有的包名佳头,然后挑選要測試的包名鹰贵;還可以要用另外的方法
第一種:用adb shell 進入android終端之后使用dumpsys activity | find "mfocusedActivity"就可以找到當前顯示在屏幕的前臺應用對應的activity,一般是第一個activity所以當前顯示程序的包名康嘉,而后面的activity就是該應用的入口activity類名碉输。
第二種:用adb shell 進入android終端之后使用dumpsys package 程序對應的包名(這個包名要自己找出來,可以參照上面的做法)亭珍,然后在顯示的信息中找到對應的報名后敷钾,再找出有ndroid.intent.category.LAUNCHER的activity,那就是我們要找的activity類名
2肄梨、查詢monkey的API阻荒,編寫腳本(具體的API可以自己上網查詢)
下面的例子是
編寫頭文件
打開瀏覽器
清空網址
輸入極客學院的網址
確認,載入網址
退出瀏覽器
然后保存到TXT
文件后再命令行monkey -f 腳本路徑+腳本文件名 運行
路徑目錄一般用/data/local/tmp/下众羡,(有點像黑白名單)
monkey服務器###
可以同與終端手機或者模擬器通過telnet
連接的電腦控制終端測試侨赡,具體看極客學院的視頻
monkey檢查內存泄漏###
可以用monkey測試后,然后用MAT檢查內存泄漏