最近在研究安卓單元測(cè)試,看到官方推薦用Espresso做UI層面的測(cè)試,就簡(jiǎn)單用了下。雖然Espresso很簡(jiǎn)單,但是適配到真實(shí)項(xiàng)目中還是走了不少彎路,踩了不少坑的。這里記錄一下:
- 由于項(xiàng)目開發(fā)的比較早,是傳統(tǒng)的Eclipse工程轉(zhuǎn)AndroidStudio工程的,因此項(xiàng)目結(jié)構(gòu)還是傳統(tǒng)的Eclipse工程結(jié)構(gòu),主代碼都在跟目錄的src文件夾下。這種情況其實(shí)很難加入測(cè)試用例進(jìn)工程,要知道Android測(cè)試分為兩種,即Test和AndroidTest,一個(gè)是用來測(cè)Java的,一個(gè)是用來測(cè)Android依賴的,例如測(cè)UI。除了文件目錄不同之外在Gradle中的引用配置也不相同,如果沒有配置好就會(huì)出現(xiàn)明明引入了第三方包,但是怎么也找不到的情況。而且需要在gradle中手動(dòng)配置test和AndroidTest的路徑,例如:
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest{
java.srcDirs = ['androidTest/src']
}
test{
java.srcDirs = ['test/src']
}
}
這樣配置之后工程就知道test的路徑和AndroidTest的路徑對(duì)應(yīng)在哪里了。但是即便這樣配置沒問題,代碼也能跑通,之后卻會(huì)遇到各種各樣奇怪的問題。這里不詳細(xì)描述了,我反正在這里踩了兩天的坑之后決定放棄了。于是我把工程整體遷移到了新的AndroidStudio結(jié)構(gòu)下,即主代碼在app文件夾下,遷移完成之后關(guān)于之前遇到的測(cè)試的坑全部解決了。因此建議想做單元測(cè)試的并且用Eclipse老版結(jié)構(gòu)工程的盡早修改為新AS結(jié)構(gòu),可以少走不少彎路!
改為AS標(biāo)準(zhǔn)工程結(jié)構(gòu)之后單元測(cè)試的坑是被填了,但是莫名其妙的跑出來性能問題。之前舊版工程開啟主頁Activity的時(shí)候很快,幾乎是秒開。新工程開啟主頁Activity竟然會(huì)白屏1~2s,嚴(yán)重的時(shí)候2~3s。甚是奇怪。拋開首頁實(shí)現(xiàn)邏輯問題不談,僅僅修改工程結(jié)構(gòu)竟然會(huì)導(dǎo)致加載性能的退后簡(jiǎn)直讓人捉摸不透。最終的解決辦法是UI分階段延遲加載。
一切準(zhǔn)備工作完成之后,本以為踩坑三天的我可以愉快的玩耍一下了,卻遇到了各種Espresso水土不服,不能正常運(yùn)行,Loading速度極慢之后還報(bào)錯(cuò)了,說AppNotIdleException。再次吐槽一下,估計(jì)國內(nèi)國外做安卓單元測(cè)試的人很少,這方面的資料真是少之又少,而且都是你抄我我抄你不說,絕大多數(shù)都是講的介紹和入門,沒有什么實(shí)戰(zhàn)經(jīng)驗(yàn)。即便是官方文檔也只是簡(jiǎn)單粗略的介紹一番。
首先推薦看Google官方的demo,地址:https://github.com/googlesamples/android-testing
這玩意跑在手機(jī)上賊流暢,而且AndroidStudio中直接支持Record Espresso Test。通常情況下大家直接用AS的RET功能不會(huì)遇到問題,即便是遇到問題,通過看Google官方的Demo也能解決了。
但是我就是遇到了AppNotIdleException問題。在網(wǎng)上搜了一圈之后也沒找到解決的辦法,遇到這個(gè)問題的人通常都是由于自己寫的測(cè)試用例有問題,而我排除了這個(gè)原因。最后又折騰了2天準(zhǔn)備放棄的時(shí)候看到了這個(gè):https://groups.google.com/forum/#!topic/android-test-kit-discuss/s9hsoZ9XdFc
后來查資料才知道原來Espresso為了保證線程安全,會(huì)等所有AsyncTask任務(wù)全部執(zhí)行完了之后才執(zhí)行。如果這個(gè)等待時(shí)間超過了60s就會(huì)報(bào)AppNotIdleException。這個(gè)時(shí)候找到?jīng)]有執(zhí)行完的AsyncTask任務(wù)就好了。
private void dumpThreads() {
int activeCount = Thread.activeCount();
Thread[] threads = new Thread[activeCount];
Thread.enumerate(threads);
for (Thread thread : threads) {
if("RUNNABLE".equals(thread.getState().toString()) && thread.getName().startsWith("AsyncTask")) {
LogHelper.e("H3c", thread.getName() + ": " + thread.getState() + "=" + "RUNNABLE".equals(thread.getState().toString()));
for (StackTraceElement stackTraceElement : thread.getStackTrace()) {
LogHelper.e("H3c", "VVV:" + stackTraceElement);
}
}
}
}
通過這段代碼,可以輸出所有正在運(yùn)行的AsyncTask,通過日志找到?jīng)]有結(jié)束的任務(wù)。我跟上面哪個(gè)鏈接中的兄弟一樣,遇到的是Facebook創(chuàng)建的某個(gè)AsyncTask一直在運(yùn)行導(dǎo)致的,最終我開了翻墻工具之后一切正常了。
總的來說,就是遇到AppNotIdleException問題之后,先排查代碼是否寫錯(cuò)了,如果不是代碼問題,可能就跟我遇到的是一樣的問題了,用上面的方法找到正在運(yùn)行的AsyncTask,然后結(jié)束掉就OK了。
此文獻(xiàn)給所有用Espresso踩坑的人。