天天看點

基于雲技術的內建測試代碼覆寫率收集的一站式解決方案

作者: 孫天衣,于清國,石俊娟,沈燕玉

背景

代碼覆寫率是衡量産品測試效果很重要的名額。得到單元測試的代碼覆寫率相對比較簡單。然而,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 folder
			String 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