標籤:產品測試 程式碼涵蓋範圍
作者: 孫天衣,於清國,石俊娟,沈燕玉
背景
程式碼涵蓋範圍是衡量產品測試效果很重要的指標。得到單元測試的程式碼涵蓋範圍相對比較簡單。然而,web應用的測試人員經常會為收集整合測試或者端到端測試的程式碼涵蓋範圍而傷腦筋。其中的主要原因是測試人員往往對這個領域的技術比較陌生,而且現有的方案比較複雜,容易出錯。舉例來講,目前有一個方案不是很自動化,需要使用者手工修改很多地方。我們經過調研,決定開發一個雲端式技術的自動的一站式解決方案來收集端到端測試的程式碼涵蓋範圍。我們開發的這個方案名為“ICoCo”(這個名稱的意思是整合測試程式碼涵蓋範圍(Integration Code Coverage))。
下面對一個程式碼涵蓋範圍的工具JaCoCo做一個簡單的介紹,因為ICoCo就是基於JaCoCo來開發的。JaCoCo一個非常突出的功能就是其能夠利用一個代理在代碼啟動並執行過程動態注入指令來收集程式碼涵蓋範圍。使用者不要提前在項目的pom檔案或者其他設定檔中做任何的修改。他們需要做的僅僅是在web容器(tomcat或其他)的啟動參數中加上一行命令:“-javaagent:${DIR}/jacocoagent.jar=output=tcpserver,port=*,address=*”當然,目錄、連接埠需要指定。 而且,程式碼涵蓋範圍的結果很容易下載下來,因為Jacoco的代理啟動了一個TCP的伺服器。請參閱附錄[3]擷取更多的有關JaCoCo的資訊。
利用持續整合工具來運行測試指令碼是目前一種非常普遍的做法。我們目前使用的是Jenkins伺服器。CI job被建立出來執行測試工作。我們的方案和Jenkins伺服器作了無縫的整合。關於Jenkins的詳細資料,請參閱附錄[1]和[2]
概述
我們不僅利用了JaCoCo的功能,而且還利用了公司內部的雲平台的API來解決應用的安裝,部署問題。
如上所述,既然所有的測試工作都是在Jenkins伺服器上執行,我們決定開發一個Jenkins 外掛程式來完成所有的程式碼涵蓋範圍的收集工作。Jenkins 外掛程式很容易與現有的測試工作整合。現有的測試工作不用做任何修改,只要建立一個使用ICoCo的CI job, 把先用的測試工作的連結配置進去即可。關於如何開發Jenkins的外掛程式,請參閱附錄[4]。 對ICoCo的設計需求如下:
1) 能夠自動把被測試的應用部署到測試的伺服器。
2) 能夠把JaCoCo的代理下載並修改web 容器啟動參數以載入代理;
3) 能夠合并子項目的程式碼涵蓋範圍的結果。
4) 能夠很容易擴充來支援不同種類的web 應用。
5) 以上所有的工作能夠自動完成。
除了以上功能需求,我們在開發過程中儘可能複用已有的庫或者服務,避免“重新發明輪子”。
目前這個方案不僅能夠支援單個應用的測試,而且也能夠支援多個應用的測試。
是ICoCo的配置介面圖。我們可以看到配置非常簡潔。
Figure1
是整合測試的程式碼涵蓋範圍在Sonar上的展示圖。可以看到,Sonar能夠把整合測試的程式碼涵蓋範圍和單元測試的程式碼涵蓋範圍很好的匯聚起來。目前公司要求所有的整合測試和端到端測試都要收集程式碼涵蓋範圍,ICoCo協助測試人員很好的完成了任務。
Figure2
ICoCo基本工作流程
1. 如有需要,部署web應用到伺服器。使用者需要在部署之前編譯和上傳安裝包。這一步是可選步驟。如果使用者針對伺服器上目前的版本測試,這一步可忽略。
2. 下載Jacoco 代理,修改啟動參數,並重啟web應用來啟動Jacoco 代理。
3. 觸發測試的CI job以進行端到端測試。在測試的過程中,JaCoCo 代理記錄下程式碼涵蓋範圍。
4. 利用JaCoCo的maven 外掛程式下載程式碼涵蓋範圍結果。
5. 如有需要,合并程式碼涵蓋範圍結果。
程式碼涵蓋範圍結果產生之後,如有需要,可以利用Sonar的Jenkins 外掛程式把結構上傳到Sonar的伺服器以進行測試品質監控。
架構和實現細節
ICoCo的實現為Jenkins 外掛程式。其架構是典型的三層架構。值得注意的地方是如果需要Jenkins plugin在一個分布式的Jenkins系統上工作,有一些特別的處理。細節請參考參考文檔[4]。因為有一些公司內部的API,這個項目的原始碼不會被公開。下面會給出一些程式碼範例作為參考。
1. 表現層 ICoCo的介面允許使用者配置各種相關參數,如是否部署,測試CI job連結等。
Jenkins是通過一個稱之為Jelly檔案的設定檔來實現外掛程式的介面開發。下面是一個執行個體代碼。具體細節可參閱參考文檔[5]。
<j:jelly xmlns:j="jelly:core"xmlns:st="jelly:stapler" xmlns:d="jelly:define"xmlns:l="/lib/layout"xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="Name" field="name">
<f:textbox />
</f:entry>
</j:jelly>
2. 控制層 ICoCo的邏輯的主要進入點為ICOCOBuilder 類中的perform 方法。ICOCOBuilder類繼承了Hudson.Task.Builder 類。各種功能,比如部署,安裝JaCoCo 代理,觸發測試,收集測試覆蓋率等等,全部實現在perform方法中。介面輸入的各個參數的值被傳遞到ICOCOBuilder中的相應的成員變數中, 以供各個功能使用。
3. 介面層 介面或者服務在各種功能執行的時候被調用。
a) 雲中負責部署的API在部署的過程中被調用;
b) 雲的管理介面被調用來下載和安裝JaCoCo 代理,沒有使用SSH直接連結。原因主要是避免輸入使用者名稱,密碼。
c) Jenkins伺服器的API被調用來觸發測試的CI job;
d) JGit庫的API被用來訪問git伺服器來下載原始碼,用以產生程式碼涵蓋範圍報告。程式碼範例如下:
public void downloadSourceFromGitRepo(File localRepoDir, String gitUrl, String commitId) throws IOException { if (localRepoDir.exists() == true) { FileUtils.delete(localRepoDir, FileUtils.RECURSIVE); } Git localRepo = null; try { localRepo = Git.cloneRepository() .setURI(gitUrl) .setDirectory(localRepoDir) .call(); localRepo.checkout().setStartPoint(commitId).setName(commitId).call(); } catch (Exception e) { RuntimeException re = new RuntimeException("Git clone failed in method downloadSourceFromGitRepo"); re.initCause(e); throw re; } finally { if (localRepo != null) { localRepo.close(); } } OutputLogger.remoteLogging("Downloading source code done.");
e) Jacocomaven 外掛程式被調用用來產生最終的程式碼涵蓋範圍報告。
下面是Jacoco Maven 外掛程式的調用程式碼範例:
-q -Dmaven.test.skip=true -Denforcer.skip=true org.jacoco:jacoco-maven-plugin:0.7.0.201403182114:dump -Djacoco.destFile=target/" + destFile + " -Djacoco.port=8084 -Djacoco.address=" + host + " org.jacoco:jacoco-maven-plugin:0.7.0.201403182114:report -f " + pomPath;
f) 合并子項目的程式碼涵蓋範圍的下載結果。合并程式碼涵蓋範圍調用了Jacoco的API。程式碼範例如下:
private static class MergeCCCallable implements Callable<Boolean, IOException> { /** * */private static final long serialVersionUID = 1L;private String rootFolder; public MergeCCCallable(final String rootFolder){ this.rootFolder = rootFolder + File.separator; }public Boolean call() throws IOException {mergeJacocoResult();return Boolean.FALSE;}public void mergeJacocoResult() throws IOException {List<File> fList = getCCFileList();if (fList.size() == 0) {throw new RuntimeException("No code coverage result file has been found!");}final ExecFileLoader loader = new ExecFileLoader();for (final File f : fList) {loader.load(f);}final File disFile = new File(rootFolder + "target/icoco.exec");loader.save(disFile, false);}public List<File> getCCFileList() {final List<File> res = new ArrayList<File>();// collect result files in the target folder of root folderString rootFld = rootFolder + "target";OutputLogger.remoteLogging(rootFld);File folder = new File(rootFld);File[] list = folder.listFiles();if (list != null){for (final File f : list) {if (f.getName().matches("^icocoTmp.*.exec")) {res.add(f);}}}return res;} }
Figure4
小結
到目前為止,端到端測試中都應用了這個解決方案,取得了不錯的效果。下載,安裝配置非常簡單,使用者不要對程式碼涵蓋範圍的收集細節有深入的瞭解,節約了大量的時間。
參考資料
1. The official web site forJenkins: http://jenkins-ci.org/
2. The description about Jenkinson Wikipedia: http://en.wikipedia.org/wiki/Jenkins_%28software%29
3. http://www.eclemma.org/jacoco/
4. http://ccoetech.ebay.com/tutorial-dev-jenkins-plugin-distributed-jenkins
5. https://wiki.jenkins-ci.org/display/JENKINS/Basic+guide+to+Jelly+usage+in+Jenkins
雲端式技術的整合測試程式碼涵蓋範圍收集的一站式解決方案