spoon+robotium+jenkins進行自動化持續迴歸測試,robotiumjenkins
自動化測試的意義: 別說是外行人,即使是正在從事自動化測試工作的人來說,現在或曾經都或多或少有過這樣的疑惑,辛苦寫了自動化測試案例,卻基本發現不了問題,其意義何在?在說明這個意義前先看下品質的定義。
品質的定義: 維基百科中對於品質(Quality)的定義:中國大陸亦稱為“品質”,可指物品的特徵、品性、本質,也可指商品或服務的水準、品質。影響品質的要素包括物品的可靠性、安全性,功能上是否完備,能否滿足需求, 等等。 對於軟體品質的定義:軟體品質,是指軟體系統或系統中的軟體部分的品質,即滿足使用者需求,包括功能需求和效能需求的程度。軟體的品質包括功能、效能、可靠性、安全性、可升級性、可維護性、其他品質特性,這也是我們一般認識中的品質,從而也指導著大多數的品質相關的測試工作 從以上的定義中也可以看出,對於品質,大家的認知更多的都是在“質”上,而較少地考慮到了“量”上,品質品質應該是質與量的結合,既關注“質”即品質保證,也要關注“量”即效率。隨著互連網的高速發展,特別是隨著移動互連網的到來,企業希望能夠更快地響應市場變化且又希望產品的品質也能夠有所保障,因此各類軟體項目提速提質相關的如敏捷、持續整合、持續傳遞、自動化等等技術就誕生了。也就是說對於品質,我們的目的不再僅僅是品質,而是真正的“品質”,即Move fast and don’t break things!。 知曉了品質的本質與目的後,再來看前面的問題就好解釋多了,自動化測試較手工測試來說,確實遠沒有手工測試更能發現問題,但正如前文所述品質的意義除了在於“質”外,還有“量”,因此,自動化測試更大的意義在於“量”上,在於提高效率上,且實際項目中,自動化也不僅僅只是把測試自動化,但凡能提高效率且適合自動化的均應該進行自動化。當然了,理論上自動化測試還是應該要能發現20%左右的問題的,對於一點問題都發現不了的自動化測試,則需要好好思考測試案例設計、測試架構搭建方面的問題了。如何更好、更快、更穩定、更可靠地進行測試自動化,這也即是本文所要分享的。在進入正題前,先看看品質的意義。
品質的意義:對於使用者,品質更多的就意味著軟體品質: 試想這麼一個例子:某天,大明正在夢鄉中與吃著美味的烤鴨,突然被震耳的手機鬧鈴吵醒了,大明前一晚由於知道明天早上有重要會議,所以特地將手機鬧鈴調早了半個小時,但也正因為如此,手機鬧鈴軟體出了點故障,比原定時間遲了半小時才響。。大明只好匆匆忙忙顧不上早餐就出門趕去上班了,且趕緊用打車軟體叫了輛車,大明心想應該還來得急,就耐心地等著出租車的到來,在瑟瑟寒風轉眼間就過去10分鐘了,大明開始急了,出租車怎麼還不來。。又過了10分鐘,出租車終於出現,原來新手出租車司機由於不認識大明的住處,因此使用了車上的導航,結果由於導航系統未及時更新地圖資料,司機開往了錯誤的地方。。意料之中的,因為沒能及時參加會議,大明挨了一頓批。快到中午了,由於心情不佳不想出去吃鈑了,大明用外賣軟體叫了個外賣,已經饑腸轆轆的大明塞上耳機聽著音樂希望可以放鬆些,結果音樂中間莫名中斷過好幾次,心情反而更糟了。。。至於下午的事就不說了,最終大明度過了鬱悶的一天。 雖然以上的大明略顯悲慘,但也決不是沒可能發生的,隨著移動互連網的到來,每個人的生活越來越需要手機及其上的軟體,每個人的幸福度很大程度地受各種軟體的影響,如果所有的軟體都能很好地運行,這個世界將變得多麼美好~
對於企業,品質則將意味著真正的“品質”: 隨著資訊的不斷爆炸式增長,我們這個時代的節奏也越來越快,特別是移動互連網,一個軟體的項目周期再也不能像傳統軟體那樣半年甚至好幾年。試想,如果你的軟體項目的持續傳遞能力比競爭者弱,那麼將意味著對方可以更快地響應市場變化、可以比你更快地將創意付諸實現,那麼很顯然地,你將失去市場,且在移動互連網,在這種情況下往往花費巨大的營銷運營代價都無法挽回頹勢。因此,提高軟體項目的整體效率勢在必行,且需要時時刻刻地去思考如何才能加快項目的運轉速度、提高持續傳遞產品的能力並且還能更有效地保障產品的品質。 “讓天下人都能用上更好的軟體”,想必這應該是品質、測試人員,甚至應該是所有軟體相關從業者的任務與使命~ 基於以上背景結合實際實踐,轉入《spoon+robotium+jenkins進行自動化持續迴歸測試》正題。。
自動化測試的原因: 例如敏捷測試中所提到的,手動測試需要太長的時間、手動過程容易出錯、自動化讓人們有時間做更有價值的工作、自動化的迴歸測試提供了安全網、自動化測試能較早且頻繁地提供反饋、測試提供文檔等等。 簡而言之,自動化測試可以提高測試效率,將重複的活動自動化後可以有更多的時間去做更多有創造性、更需要人腦思考的探索性測試上。 基於UI自動化測試的必要性:對於軟體,即使編寫了單元測試用例、組件測試案例,但這些測試並不是將軟體組合成一個整體進行整合測試的,因此很難發現最接近於使用者使用的整合測試問題,況且,很多項目團隊壓根就沒有單元測試。對於android來說,系統版本片段化較為嚴重,編寫基於UI的自動化測試案例,還可以用來進行相容性測試。
app自動化測試應該包含以下幾個基本能力:多機並存執行:同時在多台手機中執行自動化測試案例,以便在不同版本的Android作業系統中進行迴歸測試、相容性測試出錯重試及出錯:當用例執行未通過時,可以自動進行重跑並記錄,以便減少因偶然因素導致用例執行未通過的情況即時日誌記錄:對於測試執行過程應該能記錄運行時的日誌,以便詳細發解測試執行情況跨應用的能力:能夠測試包含跨應用的諸多情況產生Junit形式測試報告:產生詳細的Junit形式的測試報告,可方便查看測試案例執行結果程式碼涵蓋範圍報告:產生程式碼涵蓋範圍報告,以便進一步指導測試策略持續快速反饋的能力:對於測試回合情況,應該要能夠快速反饋易於訪問的報告:能夠很方便地訪問到測試報告詳情
spoon介紹項目地址:https://github.com/square/spoon該項目的主要目的在於將能夠將基於instrumentation的測試案例分發到各個不同的手機上,執行並將測試結果收集起來,產生最終的HTML總結報告。將項目下載下來後,進入spoon/website/sample目錄,訪問index.html即可看到樣本如下:
點擊查看後有展示功能、運行時日誌展示功能:
產生報告的目錄結構如下:
其中junit-reports收集有junit格式的xml報告,通過jenkins的junit外掛程式可以很方便產生單元測試報告簡單執行命令如下:
java -jar spoon-runner-1.1.1-jar-with-dependencies.jar \ --apk example-app.apk \ --test-apk example-tests.apk
詳細參數:
Options: --apk Application APK --fail-on-failure Non-zero exit code on failure --output Output path --sdk Path to Android SDK --test-apk Test application APK --title Execution title --class-name Test class name to run (fully-qualified) --method-name Test method name to run (must also use --class-name) --no-animations Disable animated gif generation --size Only run test methods annotated by testSize (small, medium, large) --adb-timeout Set maximum execution time per test in seconds (10min default)
註:1.由於spoon報告中的靜態頁面中使用的是googleapis中的線上字型,因此報告可能開啟會相當慢 <link href="http://fonts.googleapis.com/css?family=Roboto:regular,medium,thin,italic,mediumitalic,bold" rel="stylesheet">因此實際要做為報告時,建議翻牆後訪問http://fonts.googleapis.com/cssfamily=Roboto:regular,medium,thin,italic,mediumitalic,bold,將字型檔下載下來儲存至檔案例如放至static/fonts.css,然後靜態引用<link href="static/fonts.css" rel="stylesheet">
2.spoon中未提供開啟程式碼涵蓋範圍的Option spoon由於只是封裝了am instrument命令,最終執行的其實還是am instrument執行用例的命令,因此是可以開啟程式碼涵蓋範圍功能的,要想增加-e coverage true選項使其支援收集程式碼涵蓋範圍,則需要修改spoon源碼,最方便的那就是在源碼裡寫死選項了SpoonDeviceRunner.java中增加如下兩行寫死參數:
logDebug(debug, "About to actually run tests for [%s]", serial); junitReport = FileUtils.getFile(output, JUNIT_DIR, serial + "_" + i + "_" +".xml"); runner = new RemoteAndroidTestRunner(testPackage, testRunner, device); runner.setCoverage(true);//註:set為true後,被測應用打包時需要插樁才能產生ec程式碼涵蓋範圍統計檔案 runner.addInstrumentationArg("coverageFile", "/sdcard/robotium/spoon" + i + ".ec"); runner.setMaxtimeToOutputResponse(adbTimeout);這樣,當測試完成後,就會在sd卡中產生emma用於覆蓋率統計的ec檔案,將ec檔案pull到CI伺服器上java -cp /usr/local/program/emma/emma.jar emma report -sp $source_path -r xml -in coverage.em,$ec_files產生相應的xml檔案後,結合jenkins中的相應外掛程式就可以產生覆蓋率報告了。
3.spoon不能將測試案例集分開執行
adb shell am instrument -w -e class com.android.foo.FooTest,com.android.foo.TooTest com.android.foo/android.test.InstrumentationTestRunner
如上,我們知道通過am instrument執行用例時,可以指定多個用例集,當被測試應用啟動後,所有接下來要執行的用例集都運行在同一個進程中,如果在執行FooTest時,被測應用crash了,那麼TooTest 將不再執行,你所收集到的測試結果也就不含TooTest的結果了。當你的用例集較多時,顯然希望每次迴歸測試時能盡量把所有用例均執行過,而不希望因前面一個用例crash導致後面所有用例都沒有執行到。這時分開執行將類似如下:adb shell am instrument -w -e class com.android.foo.FooTest com.android.foo/android.test.InstrumentationTestRunneradb shell am instrument -w -e class com.android.foo.TooTest com.android.foo/android.test.InstrumentationTestRunner想要讓spoon支援這種方式運行只能修改spoon源碼了 對於以上幾點不好的地方,博主fork了源碼做了部分修改,請見:https://github.com/hunterno4/spoon 至此,通過spoon即可完成
多機並存執行、
即時日誌記錄、
產生Junit形式測試報告、收集程式碼涵蓋範圍報告等功能了
出錯重試及出錯 對於自動化測試案例,如果只是執行一次,常常因為偶然因素導致用例不能通過,如果每次收到的測試報告都是因為測試案例執行問題,顯然這樣的報告很快就不會有人願意看了。出錯重試一般性做法:1.收集測試執行時的控制台輸出,然後分析輸出是否包含某些指定關鍵字來判斷是否失敗了,然後進行重跑,這種方式是比較繁瑣的。2.根據Android junit內建的@FlakyTest實現的機制,重寫runTest重寫runTest()方法可以參考http://qa.baidu.com/blog/?p=985註:1) 重寫runTest時,需要注意的是,測試案例的執行流程如下:setUp()——> runTest()——> tearDown()這裡的重跑只是重複執行test*()方法,因此對於許多跨Activity的測試案例,例如從Activity A跳轉至Activity B,但用例在Activity B中宣告失敗了,此時重跑時,會繼續執行test***()方法中點擊跳轉到B的那塊代碼,但此時介面還處於B中,顯然是找不到A中的那些控制項的,因此在重寫runTest()方法時,需要在super.runTest()前自動地跳轉到最初的Activity。這個可以通過solo.goBackToActivity()完成2)在失敗時,我們也常常不只希望就只在斷言出錯時截那一張圖,而是希望能截取一系列運行過程的圖,這可以通過solo.startScreenshotSequence()方法完成實現方式如下:
@Override protected void runTest() throws Throwable { String testMethodName = getName(); String currentTestClass = getClass().getName(); LogUtils.logD(TAG, "currentTestClass:" + currentTestClass); boolean isScreenShot = true; boolean isScreenShotWhenPass = false; long startTime = 0; long endTime = 0; Holo holo = new Holo(getInstrumentation(), getActivity());//這裡的holo是經過增刪後的robotium,理解為solo即可 String currentActivity = getActivity().getClass().getSimpleName(); LogUtils.logD(TAG, "currentActivity:" + currentActivity); Method method = getClass().getMethod(getName(), (Class[]) null); int retrytime = 3; if (method.isAnnotationPresent(RetryTest.class)) { retrytime = method.getAnnotation(RetryTest.class).retrytime(); isScreenShot = method.getAnnotation(RetryTest.class).isScreenShot(); } LogUtils.logD(TAG, "isScreenShot:" + isScreenShot); int runCount = 0; do { LogUtils.logD(TAG, "runCount:" + runCount); try { holo.goBackToActivity(currentActivity); if(runCount > 0){//當用例第一遍執行未通過後,開啟序列,至於要截多少張圖,可以根據實際情況來設計 holo.stopScreenshotSequence(); holo.startScreenshotSequence(endTime, 5, testMethodName, currentTestClass); } startTime = SystemClock.uptimeMillis(); super.runTest(); endTime = SystemClock.uptimeMillis() - startTime; LogUtils.logD(TAG, "run test" + testMethodName + ",testcase pass with time cost:" + endTime); if(isScreenShotWhenPass){ holo.takeSpoonScreenShot(testMethodName,currentTestClass,testMethodName,DEFAULT_QUALITY); } if(holo != null){holo = null;} break;} catch (Throwable e) {if(retrytime>1 && runCount<retrytime-1){runCount++;endTime = SystemClock.uptimeMillis() - startTime;LogUtils.logD(TAG, "run test" + testMethodName + ",testcase failed with time cost:" + endTime);continue;}else {if(isScreenShot){LogUtils.logD(TAG, "takeScreenshot:");holo.takeSpoonScreenShot(testMethodName,currentTestClass,testMethodName,DEFAULT_QUALITY);}if(holo != null){holo = null;}throw e;}} } while (runCount < retrytime); }另外,spoon本身的client是不支援序列的,因此可以結合robotium本身的序列的功能,這個只要保證的目錄是spoon runner能理解的即可。
跨應用的能力 對於基於instrumentation的架構來說,跨應用能力是其弱勢,要想跨應用而又不藉助服務端的話,那基本是得root手機了。不過隨著android系統版本的不斷提高,android4.3及以上很快也就會成為主流,而4.3後基於Uiautomation就可以從容跨應用了。另外,對於基於instrumentation的架構,我們的測試工程還可以自由調用android的api來完成robotium所不具備的功能,例如完成簡訊攔截、網路切換、解鎖與鎖屏等。以切換網路為例: 當我們實現相關的功能時,可能需要添加使用者敏感的許可權,如: <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
而這樣的許可權可能在我們的被測應用中是沒有的,且也不希望在被測應用中加上這樣的許可權,因此我們只能加到自己的測試工程中。由於被測應用沒有相應的許可權,而我們的測試案例是與被測應用運行在同一進程中的,因此要想切換網路的話如果直接調用相關的api顯然是會報許可權問題的,這時我們可以通過把相關的方法封裝,然後在測試案例中通過廣播的形式來調用。而我們在自己的測試工程裡註冊接收器,當接收到相應的廣播時則調用切換網路的api,這樣的話切換網路相關的api則是運行在測試工程這個進程中,而這個進程是有相關許可權的。 簡而言之,當我們把測試工程.apk與被測工程.apk安裝到手機上時,我們既可以控制被測工程.apk這個進程,也可以控制測試工程.apk這個進程。而在測試工程.apk這個進程上,我們可以做絕大多數普通apk能做的事情,甚至於能做普通apk不能做的事情。 例如,我們可以在測試工程.apk上寫個Activity,然後還可以在測試工程.apk中寫個testcase去測試這個Activity。我們還可以在被測應用自動運行時,在測試工程中啟個service監控cpu、記憶體、流量消耗等效能指標。我們可以默默地串連網路、發送http請求,以後通過Uiautomation甚至於無需root都可以去切換設定。我們幾乎無所不能,權大,就是任性。。。
持續快速反饋的能力
測試完成後可以通過郵件反饋,對於jenkins的話,可以通過Email-ext plugin擴充郵件功能。這個外掛程式也提供很多了郵件模版,可以直接在郵件裡展樣本如svn變更集、junit報告詳情等。在選擇要採用的郵件模版前可以先測試下:
註:在jenkins中要使用郵件模版的話,需要將外掛程式內建的模版拷貝至你jenkins的workspace目錄下的email-template(預設不存在,需要自己建立)目錄下
易於訪問的報告
通過Spoon執行後的測試結果預設是收集到了spoon-output這個目錄下,且可以通過index.html靜態頁面進行訪問,因此要想方便地訪問每次的測試結果的話,部署個例如tomcat這樣的Web容器就可以了。不過考慮到維護方面且spoon-output是靜態,那就完全沒必要再弄個容器了,jenkins有個userContent目錄,放於這個目錄下的所有檔案都可以通過jenkins直接存取。因此我們可以把每次測試完成後產生的結果檔案自動copy到userContent下,且按照每次構建的id來存放。例如:http://192.168.10.111:8080/jenkins/userContent/spoon/162/spoon-output/index.html
這樣在每次測試完成後,收到反饋郵件時就可以直接通過郵件中的連結訪問測試結果了。
總結: 至此,spoon+robotium+jenkins進行自動化持續迴歸測試就可以滿足基本的核心需求了,當項目每天check in有新代碼時,我們通過這些自動化測試案例在多台手機上進行迴歸測試,測試完成後收到郵件,若有問題時,訪問測試結果詳情頁,根據與當時的運行時日誌判斷是否有bug,若有的話,即時反饋進行bug修複。這樣的話,我們即可在多台手機上同時完成迴歸測試與相容性測試,手機越多效益越大。當然了,自動化持續迴歸測試只是提升“質”與“量”的一部分,對於測試團隊來說,介面測試自動化、測試環境營運自動化、效能監控等等等等還有很多。如何提升項目的效率這是永恒的主題,而對於傳統的更多地只關注品質的測試團隊來說,在新時代下也將越來越難以滿足企業對效率的追求,想必這也是google工程生產力團隊之所以誕生的本質動因。 願景:對於軟體業來說,也許目前的自動化測試仍然還是比較初級的,但正如萊特兄弟發明飛機時,只能飛很小的一段距離,但經過多年幾代人不斷的付出與努力後,飛行工具已經給人類帶來了巨大的效益,即使往更近了看,製造業的自動化已經將眾多產業的的流水線變成了自動化,從而大大提高了製造工業的生產力,因此,相信軟體業的自動化也將極大地提高軟體項目的生產力。