實現Android CI

CI在web-based application上已經有了很是成熟的實現,由此也積累了大量的優秀實踐。但這些實踐在Android平臺中是否適用? 已知的CI工具是否可以很好的支持Android? Android CI是否也能即時的反饋Android Application的健康情況? 這篇文章中將經過實踐,向你們展現Android CI可用的實現方法,是時候爲咱們的Android App搭建CI了。 html

1、Android CI帶來了什麼?

1) App質量的提高

事實上CI並不能直接提升App的質量,可是CI提供了對App的監測和反饋,經過持續的檢測和反饋,能夠完成對App的持續改進。 java

在CI的Compilation階段,若出現編譯失敗頻率較高,一是由於代碼未按照原子提交的原則進行,二是本地開發環境不乾淨,存在與CI環境不一致的地方,致使每次提交時不能提交全部文件,老是須要手動挑選提交文件。 web

在CI的Testing階段,若出現失敗,頗有可能說明這次提交已經破壞了與之相關聯的功能或者模塊。App經過Testing,就是App可用性的一種反饋。 shell

CI的Inspection階段會對代碼作多方面的考察,如Checkstyle,單元測試覆蓋率,代碼靜態bug分析等,這些都是對代碼質量的檢測,經過這些改善檢測結果,代碼質量也就會隨之獲得提升。 編程

CI將各環節的結果反饋給整個團隊,團隊爲改善這些結果付出努力後,App的質量天然也就獲得了提高。這也就是爲何反饋環節在CI是如此的重要,以致於失去它,CI沒法發揮任何做用。 bash

2)風險下降

CI將項目當前的健康情況即時的通報給整個團隊,使得項目情況變得十分透明。團隊成員在得到CI的反饋後,會關心項目的健康情況,逐步的團隊全部人都習慣爲App質量承擔起本身的責任。 服務器

Bug的出現老是不可避免的,那就但願這些bug儘早的出現。持續的進行Testing可讓bug儘量早的被發現。同時很快就能定位bug引入時間,並解決它。在發佈前夕,發現App居然存在部署問題,這必定會讓你們變得緊張起來。持續的進行Deploy,可讓這些問題儘早的暴露出來並解決。 app

3)重複步驟減小

爲了獲得一個可發佈的Android App,須要經歷編譯,測試,驗證,部署等衆多步驟。爲了節約出包的時間,減小對資源的消耗,保證步驟都被正確執行。CI能夠幫組構建一個能夠重複執行的出包流程,並經過不斷的優化,縮短期。這樣一個肯定的出包步驟可以讓開發人員在本地環境快速的完成部署。 框架

4)對產品的信心加強

項目在一個透明的環境中運行着,團隊成員都關注這App質量,努力改善項目健康情況。這樣一個齊心合力的團隊狀態,相信整個團隊對於項目的信心也會隨之增長。 工具

2、Android CI面臨的困難

Android環境存在不穩定。Android的模擬器在虛擬機中存在不能穩定運行的情況。

自動化框架不夠成熟。尤爲是自動化測試。

沒法完成自動化部署。

Android設備中有些性能較低。須要在這些設備上運行Function Test時資源緊缺就更加明顯。

Android正在快速發展,帶來了多個差別較大版本。那CI就應該對這些版本都可以兼容。

3、一步步實現Android CI

Android上的CI構建鏈與其它平臺一致,依然包含Compilation, Testing, Inspection, Deploying階段,每個階段的Feedback的都保持對整個團隊透明。

CI中各個步驟執行前後順序的安排,應該是執行時間較短的優先執行。執行時間短的通常在提交代碼前就可執行,錯誤率也比較低,就應該儘量先執行。這樣失敗會來得更早一些,每一次CI運行失敗前驗證完畢的東西更多。上圖中CI的工做流,正是在這樣的一個原則的基礎上造成的。

環境準備

* 在CI服務器上安裝Java和Android運行環境

* 安裝構建工具,本文采用Ant進行實踐

* 搭建好CI服務。本文采用開源的CI服務Jenkins(Hudson)。

* Jenkins在功能上徹底可以知足功能上的須要,且簡單易用。

* 安裝Ruby環境。本文中使用的Functional Test測試工具是基於Ruby實現的。

步驟 1:持續構建

持續構建的目的是隨時可自動化生成最新的可運行的App。雖然有這麼多限定詞來表示這一步完成的驗證條件,但事實上只須要通過三個步驟便可完成。

一是更新代碼,Jenkins中已經很好的支持了SVN和Git這兩項經常使用的代碼管理工具。二是採用構建腳本構建安裝包,Android已經很貼心的連Ant構建腳本都爲咱們準備好了,而且由於Android的包結構的規範,也很大程度上消除各開發人員環境下項目機構的不一致。三是持續執行前兩步,只有在每一次出現任何代碼變更時當即執行前兩步才能保證隨時均可以提供可運行的安裝包。

持續構建實現起來比較容易,可是它所達成的效果仍是很不錯的。對開發人員來講,均可以採用同一個腳本快捷的在本地生成安裝包,這在很大程度上也減小了出現「這在我機器上運行的很好」的問題。對於測試人員,隨時均可以獲取最新的測試包,不須要再等待開發人員騰出時間來作這件事。對於產品人員,能夠利用這些最新包,在開發人員完成後第一時間得到反饋。甚至能夠在完成部分功能的狀況下就開始體驗了。

Best Practice:

* 在每一次提交後都對整個project進行構建。這裏的提交應該包含任何一個微小的改動。

* 全部人遵循相同的構建順序,採用同一套構建腳本

* 每次構建的時候都執行同一套腳本

步驟 2:持續測試

持續測試是快速的經過自動化的手段收集軟件健康情況的方法。持續測試是爲了驗證構建完成的包功能是否可用,而不只僅可以安裝運行。對App的測試能夠從UI, Function, Code三個層次來進行,這三者間的權重關係能夠參照測試金字塔來設計。

根據前文提到的優先運行最快的原則,這三個層次的測試,應該按照Unit Test, Functional Test,和UI Test的前後順序安排在CI執行。

一、添加Unit Test

Unit Test是運行成本最低的測試,而且對於測試用例覆蓋最爲全面。鼓勵儘量利用單元測試覆蓋用例。Java中的單元測試首選的仍是使用JUnit,但Android project的代碼由於對SDK存在着極強的依賴,僅僅使用JUnit進行單元測試,可以覆蓋的代碼實在太少。爲了解除對SDK的依賴,天然會考慮引入Mockito這樣的Mock框架。但即便藉助Mockito寫單元測試的工做量依然巨大,由於須要mock的對象實在太多。而且Android的object在JVM中沒法建立。

這時能夠採用Robolectric單元測試框架,這將大幅度提高單元測試覆蓋率,且理論上能夠達到100%。Robolectric是以JUnit爲核心,完成了對Android SDK的stub。採用stub的方式後,Android的組件在JVM中便可建立並運行,無需在Android平臺下運行。這也意味着在Android開發中能夠採用TDD的方式,進一步提升單元測試覆蓋率。該框架的使用JUnit徹底同樣,運行性能也一致。

因爲Robolectric對SDK進行了stub,在寫單元測試時徹底能夠對組件狀態進行驗證,甚至能夠對組件進行操做。下面這個測試就是對button點擊事件的測試,而且驗證了Activity的狀態。

@Test
public void should_be_finished_if_clicked_on_cancel_button() {
    Robolectric.application.onCreate();

    CustomActivity activity = new CustomActivity();
    Intent newIntent = new Intent();
    activity.setIntent(newIntent);
    activity.onCreate(null);

 Button cancelButton = (Button) activity.findViewById(R.id.cancelButton);
    cancelButton.setVisibility(View.VISIBLE);

    Robolectric.clickOn(cancelButton);

接下來的工做就是將Robolectric集成到CI中,讓它檢查程序的健康情況。Robolectric本質上仍是JUnit,只是多了一些stub 對象而已。那咱們集成Robolectric的方法和JUnit徹底一致。只需建立Ant task,並在Jenkins中執行此task便可。此Ant task以下:

<target name="unit-test" depends="clean, init, compile">
<junit fork="yes" dir="." failureProperty="
test.failed" haltonerror="false" haltonfailure="false"
printsummary="yes" forkmode="perBatch"
showoutput="no">
<classpath location="${cobertura.instrumented.dir}"/>
<classpath>
<pathelement path="${tested.project.dir}/bin/classes"/>
</classpath>

<classpath location="${classes.dir}"/>
<classpathrefid="test.classpath"/>

<formatter type="brief" usefile="false"/>

<formatter type="xml"/>
<batchtesttodir="${reports.xml.dir}">
<filesetdir="${src.dir}">
<include name="**/*Test.java"/>
<exclude name="**/AllTests.java"/>
</fileset>
</batchtest>
</junit>

<junitreporttodir="${reports.xml.dir}">
<filesetdir="${reports.xml.dir}">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="${reports.html.dir}"/>
</junitreport>
<fail if="test.failed" message="Unit test(s) failed."/>
</target>

在將這些測試集成至CI後,最重要的一步收集結果是不能忘的。以前已經說過Calabash也可按照單元測試報告規範輸出,加上Robolectric自己就是JUnit框架的擴展,報告也是按照單元測試報告規範輸出。Unit Test和Function Test的報告便可使用JUnit test收集。

要想得到單元測試覆蓋率報告,Cobertura是個不錯的選擇。添加

<target name="coverage-report">
    <cobertura-report srcdir="${tested.project.src}" destdir="${cobertura.coverage.xml.dir}" format="xml"/>
</target>

<target name="summary-coverage-report">
    <cobertura-report srcdir="${tested.project.src}" destdir="${cobertura.coverage.summaryxml.dir}" format="summaryXml"/>
</target>

<target name="alternate-coverage-report">
    <cobertura-report destdir="${cobertura.coverage.html.dir}">
        <fileset dir="${tested.project.src}">
            <include name="**/*.java"/>
        </fileset>
    </cobertura-report>
</target>

從Jenkins上便可得到清晰的單元測試覆蓋率的報告

二、添加Function Test

Android爲你們提供了一套集成測試框架Android integration testing framework。但此框架未集成Cucumber,這致使每增長一個Function Test都須要較大的開發和維護工做。這樣高成本的實現Function Test將大大延緩開發進度,最終由於項目進度的緣由致使Function Test被丟棄。產生這樣的後果那必然是不肯意看到的。

目前Android平臺下已經出現多種Functiong Testing測試工具,如Native Driver, Robotium, Calabash等。在嘗試對比後,最終選擇了Calabash Android做爲解決方案。Calabash Android是Cucumber在Android平臺的實現,使用Ruby書寫Function Test,並提供了一組操做Anadroid App元素的API。

Calabash Android的主要優點有如下三點:

a. 對於BDD的支持

b. 使用Ruby實現Function Test更加的符合天然語言的習慣。使得QA也能輕鬆的實現Function Test

利用Calabash提供的對App組件操做的API,實現啓動App並登錄只須要如下短短的幾行代碼:

Given /^I launch and login TelstraApp$/ do
step %Q|I launch the app|
step %Q|I wait to see "MY ACCOUNT LOGIN"|
step %Q|I enter "#{$username}" into input field number 1|
step %Q|I enter "NotARealPassword" into input field number 2|
step %Q|I press "Login to My Account"|
end

c. 支持Android和iOS使用相同的API操做App。使得iOS和Android平臺中的Feature能夠重用

Jenkins集成Calabash Android

運行Calabash Android須要Ruby環境,同時也建議安裝RVM。在CI agent上安裝Ruby和RVM,併爲Jenkins安裝RVM plugin後運行環境就準備好了。

在Jenkins中執行運行Calabash Android的shell命令前須要注意指定運行時的gemset

Calabash Android在Jenkins中的執行命令以下:

Calabash在運行完畢以後,能夠按照單元測試報告的規範提供測試報告

三、添加UI Test

Android在新近退出了UI測試工具UIAutomator。此工具僅支持Android4.1及以上平臺,鑑於目前市場上2.3和4.0版本仍佔主導的狀況來看,目前還沒法知足你們的須要。另外應用該工具實現UI測試的開發成本還較高,筆者暫不推薦使用此工具,但應該關注其發展。

另外基於錄製回放機制的測試方法一樣能夠進行UI測試。但錄製回放的方法在面對功能快速迭代時,維護工做會急劇增長,而這個維護成本能夠說是很難承受的,因此在此也不會將這種測試方法集成至CI中。

目前來看Android中UI測試還無使人滿意的方法。若對UI成功比較看重,能夠投入精力應用UIAutomator進行UI測試。

Best Practice:

* 將測試按照單元測試,組件測試,功能測試和系統測試進行劃分。單元測試應該在每次提交時觸發執行,其它的測試根據運行時間長短和重要程度能夠每次提交觸發執行或者定時週期執行。

* 將運行較快的測試優先執行。

* 讓功能測試可以重複執行。不然維護成本過高,會被捨棄。如果後臺數據致使不可重複,能夠將數據抽象成爲數據集,在每次運行前進行重置。

* 書寫測試時每個assert只作一種判斷,這樣能夠明確每次測試的目的,而且能夠快速定位測試失敗願意。

步驟 3:持續檢查

持續檢查是對於代碼自己檢測和反饋。檢測主要經過對代碼靜態分析驗證代碼風格,編程規範,代碼複用,代碼語言中的Best Practice等多個維度的代碼質量。

Sonar做爲一個開源的代碼質量檢測工具,涵蓋了7項代碼質量檢測方式。這充分知足Android平臺下對於代碼質量的檢測分析。Sonar分爲兩部分一部分是代碼分析工具,另外一部分是數據分析展現的Server。

Sonar可進行的分析維度在其Dashboard中能夠看見:

Sonar的分析工具也有多種運行方式,能夠由Ant script, Jenkins plugin, jar等多種方式運行,爲了簡化Jenkins的配置,本例子採用Ant script的方式運行:

<target name="sonar">
<taskdef resource="org/sonar/ant/antlib.xml">
<classpath path="CISetup/sonar-ant-task-2.0.jar"/>
</taskdef>
<sonar/>
</target>

Best Practice:

* 將測試覆蓋率,代碼分析結果透明化

* 持續下降代碼複雜度

* 持續的促進設計的演進

* 持續的維護代碼結構

* 持續減小代碼重複

步驟 4:持續部署

因爲Android App採用用戶手動從Appstore自行下載安裝的方式發佈,使得Android App沒法直接部署至用戶手機中。另外Appstore須要對於上線的App進行審覈,不能持續進行Release。於是Android中持續部署將以持續發佈可安裝包爲目標。

在以上目的下,只需根據自身項目資源找到合適的安裝包管理工具便可。如本文采用Dropbox來管理全部安裝包。

Dropbox做爲一個雲存儲平臺,在Android終端設備上能夠輕鬆下載存放在其中的文件,同時上傳安裝包也能夠交由Dropbox本身完成。

<target name="copy-prod-apk-to-dropbox" >
<echo message="copying ${basedir}/bin/AndroidApp-prod-${VERSION_NUMBER}.apk to
 ${dropbox.dir}/AndroidApp/R4/${VERSION_NUMBER}/" />
 
<copy file="${basedir}/bin/AndroidApp-prod-${VERSION_NUMBER}.apk" todir=&
 quot;${dropbox.dir}/AndroidApp/R4/${VERSION_NUMBER}" overwrite="true" />
 
</target>
Best Practice:

* 保證任什麼時候間都可以發佈可用的程序

* 爲每一次build打上build號

* 執行部署籤運行全部的測試

* 保證部署失敗都能執行回滾

步驟 5:持續反饋

反饋是全部改進的開始,必需要讓全部人獲取到他們所關心的反饋信息,才能實施改進。持續反饋的目的就是讓全部人都掌握項目健康情況。項目全部人事實都是有意願知道項目當前的健康情況的,那CI就應該將項目的狀況作到透明,並將不一樣的反饋通知到各相關的成員。

CI不一樣階段產生了不一樣維度的反饋,如單元測試報告,測試覆蓋率等。本實踐中將這些反饋都透明的展現在項目首頁中。之因此沒有將這些反饋再以郵件的方式通知全部人,是由於團隊成員已經養成了查看CI的習慣。

若是說只給全部人發一封郵件說明項目情況,那必然是告訴全部人「CI全部步驟是否都返回正確?」。這樣一個反饋,包含了編譯正確,全部測試經過,安裝包已經準備完畢等重要信息。有必要讓全部人都知道這個信息,特別是在CI執行失敗的時候。Jenkins自身已經提供一個簡單有效的透明化方法,以項目爲藍色表示經過,紅色表示有步驟失敗。

反饋的通知方式有不少種,不必定要採用郵件通知的方式。能夠尋找更加有趣的方式,若是播放音樂和設置警報燈。在每一次Build成功或失敗後都播放一段有趣的音樂,打開不一樣顏色的警報燈,這兩種方法都是是一種簡單有效的方式,可讓項目全部人都獲取到最爲關鍵的信息。

Best Practice:

* 向全部團隊成員公佈CI反饋

* 造成持續的發佈CI反饋的機制

4、結束語

從本文的實踐來看爲Android項目搭建CI與其餘類型項目步驟基本一致,所不同的是各步驟中依賴的實現技術而已。確實也由於Android CI所依賴的技術的不夠成熟,存在一些支持不足的狀況,如對UI的測試,影響了CI的價值。但它CI的價值依然值得花時間去搭建。

做者簡介

朱傲,北京交通大學碩士,現爲Thoughtworks諮詢師。關注軟件質量、移動開發、知識管理等領域,喜歡技術寫做及分享。

相關文章
相關標籤/搜索