龔永生 (gongys@legend.com)北京市海淀區上地信息產業基地開拓路7號聯想大廈
本文有兩個目的:實現每晚構建平臺和探討一個軟件從需求文檔到設計文檔的書寫規范。
1、前言
本文有兩個目的:實現每晚構建平臺和探討一個軟件從需求文檔到設計文檔的書寫規范。
每晚構建是軟件研發管理中極具價值的手段,對于加快發現和改正缺陷,降低集成風險,提高產品質量,加強成員溝通與協作,縮短產品上市時間,增加項目開發透明度,提高項目組成員信心和斗志有著非常重要的作用和意義。本文從軟件工程過程:需求定義,分析,設計出發描述了實戰每晚構建平臺的大部分過程。
軟件工程中文檔有著極其重要的地位,良好的文檔風格和習慣是一個團隊成熟的重要標志。目前有些軟件研發人員特別是剛剛走上崗位的研發人員對文檔書寫沒有一個統一的概念,他們不知道需要寫那些文檔,他們不知道每個文檔需要寫到什么地步,一提到寫文檔就抓耳擾腮。本文試圖在這個方面提供一個文檔樣例,我不認為我的文檔寫的很好,只希望能起到拋磚引玉之功效。
為了實現這兩個目的,本文首先解釋了每晚構建,讓讀者對這個術語有個比較清楚的認識;然后借助于《編寫有效用例》中的用例編寫技術編寫了每晚構建平臺的用例并附加了其他相關需求形成了一個相對使用的需求定義文檔;接著用面向對象的系統分析方法對需求定義進行了問題空間分析,構造出分析模型;為了讀者更好更快地理解設計,后面是一整節的相關開源或第三方技術介紹,有類似于"Hello world"的入門介紹,也有精髓內容解析,還有注意點提醒;接著是平臺兩大系統的設計。
整個每晚構建平臺包括兩大子系統:構建系統和構建信息顯示系統。構建系統用ANT構建腳本實現;構建信息顯示系統是個典型的web應用。兩者都采用了面向對象的分析和設計技術。
需求定義的閱讀應該以用戶為主,文檔撰寫者為輔,需求定義完成的標志是兩者達成了一致;分析章節的閱讀應是文檔撰寫者幫助用戶閱讀,使用戶理解并相信分析模型是能解決用戶的問題的;設計章節的閱讀主要是系統實現者,除了要求可實現性外,設計模型還要和分析模型有很強的繼承性和一致性。
整個文檔分為上中下三篇,上篇主要講述需求定義和分析模型。
2、每晚構建的定義和作用
構建是從代碼庫中取出一個開發中的軟件項目的所有最新代碼,放在一個干凈的環境下面,編譯源代碼文件,連接,安裝和測試,并記錄整個過程中所有日志的動作系列。
構建平臺是一個構建和構建信息展示的系統。
每晚構建主要是指在開發活動不太激烈的情況下,盡量不要干擾開發者的正常開發工作,保證每天執行構建一次。
在《cvs和nightlybuild技術》一書中提到了每晚構建的作用:
每個模塊有一部分成果集成一部分成果可以大大地降低集成風險,加強模塊間協作性錯誤的診斷,降低整個系統的不確定性,可以更好地定位錯誤從而加快開發速度,促使模塊間的接口規范而增強團隊合作,每天都有新系統新成績是對每位項目組成員的一個重要激勵。
除了這些作用之外,我們還可以從每晚構建中為項目管理提供額外的信息:
每晚構建的產品,每晚構建過程的記錄,每晚構造過程中的測試記錄,分析測試質量的測試覆蓋率分析,構建對象中項目組成員的貢獻和項目組成情況分析(本文稱為項目度量)。
3、需求定義
關于需求定義的文檔我們一般稱之為《需求規格說明書》,這個文檔的任務在于比較明確地確定項目的目的和范圍,提出功能需求和非功能需求,功能需求也叫行為需求,一般可以用用例來描述,如果所有需求的全集我們稱為問題域,則功能需求我們可以稱之為問題域的本質。軟件研發公司一般都有《需求規格說明書》的框架,規定了書寫需求定義文檔的"綱",下面就普遍格式對需求進行描述。
1.目的和范圍
每晚構建平臺是提供一個自動化的信息系統,用來輔助構建者構建系統,盡量做到自動化,最大化地減輕構建者的工作負擔;記錄構建過程的詳細記錄,給項目經理、組織層次的軟件開發過程改進人員評估和管理軟件開發項目提供相關數據,為項目成員和組織領導增加軟件項目開發的透明度。
2.術語
構建信息關心者是關心構建過程的記錄信息的人員,包括項目經理、過程改進人員、項目成員和組織領導。
3.用例
根據我們前面的構建平臺的定義,實現這樣的系統主要有兩個用例和兩種角色。如下圖:
- 用例一:構建
名稱:構建
級別:user goal
范圍:每晚構建平臺
主要角色:構建者
前提:主要角色已登錄
成功場景:
- 構建者要求系統從代碼庫中取出某個項目的所有源代碼
- 構建者編譯連接所有源代碼,系統產生項目軟件
- 構建者要求安裝構建出的項目軟件,系統安裝軟件
- 構建者測試軟件,系統測試并記錄測試信息
擴展:
* 系統出錯:
*.1系統保存出錯信息
- 用例二:瀏覽構建信息
名稱:瀏覽構建信息
級別:user goal
范圍:每晚構建平臺
主要角色:構建信息關心者
前提:已執行構建
成功場景:
- 構建信息關心者進入構建信息地址
- 系統顯示所有的構建信息的目錄
- 相應信息選擇感興趣的構建信息
- 系統顯示相應的信息
擴展:
* 系統出錯:
*.1系統保存出錯信息
4.技術
- 要求用web技術,采用j2ee體系;
- 采用cvs open souce系統作為項目源代碼版本控制工具;
- 數據字典
構建信息:構建出的產品;單元測試日志(成功與否,失敗則要提供相應失敗原因);單元測試的覆蓋率;構建過程的日志;項目組成員的工作量(代碼行數)和項目源代碼樹中各個目錄和非二進制文件的行數;項目源代碼樹。
- 其他需求
- 盡量采用第三方開源代碼軟件;
- 盡量使構建用例自動化;
- 能構建多個項目;
- 實現用java 語言寫的項目的構建,但要考慮未來對c,c++程序的構建的擴展。
4、系統分析
系統分析要對問題空間的本質進行分析造模,形成"要做什么"的深刻了解,盡可能地和用戶達成共識。我這里既使用了傳統的造模工具也利用了面向對象分析的方法學來產生文檔。這一節中我首先對這些需求所涉及的子系統進行定位,看看他們之間的關系,這里我把這種分析稱為上下文分析;然后看看我們關心的子系統數據流如何,得出數據流圖;最后我分析每晚構建平臺的"類"模型。本節所有內容的全體構成了對需求的分析模型。
1.系統上下文分析
通過分析,我們可以看出整個每晚構建平臺的運作由6個實體構成,它們之間的關系如系統關系圖所示。
構建系統完成需求定義中的軟件項目的構建工作,它從CVS系統中獲取項目源代碼,并對源代碼進行編譯連接安裝和測試工作,產生的構建信息通知給構建信息展示系統,構建信息關心者通過構建信息展示系統瀏覽構建信息。用藍色標記表示的為本平臺的組成部分。
2.數據流程圖
下面的數據流程圖更好地更詳細地理解了需求,從這個流程圖可知,構建系統主要和CVS系統打交道,并會生成構建信息供構建信息展示系統使用。構建系統由取出項目代碼、產生項目度量數據、編譯連接安裝測試刪除和編譯連接安裝(發布版)四個任務組成;構建信息展示系統和構建信息關心者打交道,主要完成瀏覽構建信息的任務。用黃色線條表示的存儲單元"項目源代碼目錄"是個臨時存儲單元。
最后需指出的是本分析模型實現的用例一為全自動版本,是比較符合"最大化的減輕構建者工作負擔"的需求的。
類模型
- 類關系
類BuildAdmin,ProjectBuild是project類的子類,因為從某種意義上來說,它們兩者都是項目,不把OSScheduler類列為project類的子類是因為OSScheduler類很簡單,可能是操作系統的一行腳本或者一行配置。
另外BuildAdmin負責所有ProjectBuild服務execute方法的執行,所以它和ProjectBuild的關系為一個復合關系;OSScheduler類要啟動BuildAdmin的execute方法,所以OSScheduler和BuildAdmin有單向關系。
另外構建信息存放的位置是本平臺的關鍵,為了讓所有的類對這些位置有一個共同的視圖,BuildInfoLog類是其它類的父類。
- 類描述
經過分析不難知道,所有的構建信息用非結構化數據來描述比較合適。在這里可以做一個決策:要求他們是獨立的,也就是說進入相應的信息地址,它們可以自我顯示;需求定義要求用www的方式瀏覽這些信息,所以同時要求他們對普通的browser是可瀏覽的。我們沒必要用類來描述這些信息。
構建顯示信息系統只有一個類來顯示構建信息目錄:每晚構建顯示類(NightlyBuild)
另外從上下文圖和數據流程圖,我們可以得到我們構建系統的類:操作系統定時服務(OSScheduler),構建管理服務(BuildAdmin),應用項目構建服務(ProjectBuild)。
兩個系統之間有一個定義構建信息存放位置的類BuildInfoDir,這個類定義了兩個系統之間的協議。
注意書寫約定:
${變量名}為取變量名所代表的值的含義。
- 構建信息存放位置類
類名 |
構建信息存放位置類 |
類英文名 |
BuildInfoDir |
成員變量 |
變量名 |
變量說明 |
nightly_Build_Tags |
保存所有的構建標簽,是構建標簽列表 |
logTopDir |
保存構建管理服務運行的日志的目錄 |
statCVSTopDir |
保存了所有應用項目的項目度量結果的頂層路徑 |
projectLogTopDir |
保存了所有應用項目構建服務實例運行日志的頂層路徑 |
testCoverTopDir |
保存了所有應用項目的測試覆蓋率計算結果的頂層路徑 |
distTopDir |
保存了所有應用項目的發布版的頂層路徑 |
testTopDir |
保存了所有應用項目的測試結果的頂層路徑 |
方法 |
方法名 |
參數 |
執行步驟 |
方法說明 |
-
每晚構建顯示類
類名 |
每晚構建顯示類 |
類英文名 |
NightlyBuild |
成員變量 |
變量名 |
變量說明 |
方法 |
方法名 |
參數 |
執行步驟 |
方法說明 |
list_ Buildinfo_table |
. |
(1)讀取nightly_Build_Tags內容,格式化顯示每個構建標簽指示的構建信息目錄。 |
. |
-
操作系統定時服務
類名 |
操作系統定時服務 |
類英文名 |
OSScheduler |
成員變量 |
變量名 |
變量說明 |
方法 |
方法名 |
參數 |
執行步驟 |
方法說明 |
excute |
. |
- 利用系統當前時間形成日志文件名;
- 執行BuildAdmin的excute()方法,并把BuildAdmin的正常輸出和錯誤輸出記錄到日志文件名中;
- 保存日志文件到${logTopDir}指定的目錄中。
|
啟動構建管理服務并記錄日志 |
-
構建管理服務
類名 |
構建管理服務 |
類英文名 |
BuildAdmin |
成員變量 |
變量名 |
變量說明 |
cvsroot |
保存了cvsroot環境變量 |
buildDir |
保存了臨時存放應用項目源代碼的路徑 |
|
方法 |
方法名 |
參數 |
執行步驟 |
方法說明 |
cvs_check_out |
應用項目cvs系統中的名字 |
- 利用cvsroot 登錄cvs系統;
- 執行
cvs co 指令,把參數制定的應用項目源代碼取出并放在成員變量 ${buildDir} 指定的目錄/項目名字/目錄下。
|
從cvs中取出項目源代碼 |
statcvs |
- module:應用項目cvs系統中的名字;
- project_Build_Tag:每個項目的當前構建標簽
|
(1) 對源代碼進行項目度量,并把結果放在${statCVSTopDir}目錄下的${project_Build_Tag}目錄下。 |
對源代碼進行度量 |
execute |
. |
對每一個應用項目執行:
- cvs_check_out
- statcvs
- 執行ProjectBuild的execute()方法
|
. |
-
應用項目構建服務
類名 |
構建管理服務 |
類英文名 |
BuildAdmin |
成員變量 |
變量名 |
變量說明 |
project_Build_Tag |
保存了當前正在構建的項目由項目名稱和當前系統時間組成的構建標簽 |
方法 |
方法名 |
參數 |
執行步驟 |
方法說明 |
test_project |
. |
- 為測試而編譯連接源代碼;
- 安裝測試版產品;
- 測試產品;
- 計算測試覆蓋率;
- 輸出測試結果到
${testTopDir} 指定目錄下的 ${project_Build_Tag} 目錄下;
- 輸出測試覆蓋率結果到
${testCoverTopDir} 指定目錄下的 ${project_Build_Tag} 目錄下;
|
. |
dist_project |
. |
- 編譯連接源代碼;
- 安裝發布版產品到
${distTopDir} 指定目錄下的 ${project_Build_Tag} 目錄下。
|
. |
execute |
. |
- 執行test_project方法
- 執行dist_project方法
- 把兩個方法的日志合并成一個日志文件,命名為
${project_Build_Tag}.txt ,并把其放在 ${ projectLogTopDir } 指定的目錄下。
|
. |
- 執行場景
- 構建場景
- 1.操作系統定時服務類開始執行;
- 1. 1啟動BuildAdmin對象的execute方法
- 針對每個被管理的項目,執行下列步驟:
- #begin
- 1.1.1根據當前系統時間和項目名稱生成該項目的構建標簽,并記錄構建標簽
- 1.1.3以構建標簽為參數生成項目的ProjectBuild對象
- 1.1.2調用cvs_check_out方法,從cvs系統中取出該項目的源代碼
- 1.1.3執行statcvs方法,生成項目度量數據并保存到相應的位置
- 1.1.4調用該ProjectBuild對象的execute方法,完成項目的測試和安裝,并產生相應的構建信息
- #end
- 瀏覽構建信息場景
我們已經說過,各個構建信息能實現在browser中的自我展示,所以Nightlybuild對象只需通過某種格式顯示各個構建信息的目錄,構建信息請求者可以通過這些目錄請求各個構建信息。
1.NightlyBuild對象接到瀏覽構建信息的請求,通過對自己保存的構建信息目錄和構建標簽列表組織構建信息目錄。

 |

|
5、文檔書寫輔助工具
- word 文檔書寫排版工具
- powerpoint,圖片組織繪畫工具
- visio 繪制數據流圖,ER圖等的工具
- rational rose,繪制UML圖形的工具
- windows 附件中的畫圖來截取圖片
- 操作系統的全屏打印功能
本文是實戰每晚構建系列的第二篇,主要敘述在設計構建平臺時要考慮的一些開源或第三方技術,其中既有有類似于'Hello world'的入門介紹,也有精髓內容解析,還有注意點提醒。
1、相關開源或第三方技術
在進行設計之前,我們有必要了解一些開源或第三方在項目構建方面的技術。學習這些技術的最好方式是弄到一份,仔細閱讀文檔,實踐一些小的例子,在工作當中使用之。
1.1 ant 項目構建工具
為了讓大家更好地了解后面的設計,本節出了介紹基本知識外,還介紹了這個工具的主要特點中的三點:多個文件組成配置文件,目標依賴性,擴展,另外講述了ant配置腳本的面向對象特性。
- 簡述
Ant是Apache開源運動中的一份子,它是和大家所熟悉的Make系統一樣的基于Java的構建工具。他克服了Make的os依賴性,同樣也可以調用os特有的指令。不像Make使用操作系統的腳本命令,ant使用java 類來擴展自身。Ant的配置文件是流行的xml格式。
下面是一個最簡單的build.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="projectTemplate" default="init" basedir=".">
<target name="init" >
<property name="lib.dir" value="lib"/>
<echo message="Hello ${user.name}! lib.dir is set to ${lib.dir}" >
</echo>
</target>
</project>
|
運行ant命令將產生下面的結果:
gongys$ ant
gongys$ Hello gongys! lib.dir is set to lib
|
在這個簡單的build.xml顯示了ant配置文件定義目標(target),定義屬性(property),訪問屬性的方法,其中${user.name}是個系統屬性。
- 多個xml文件定義ant配置文件
下面我們給出一個相對復雜的build.xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE project [
<!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">
]>
<project name="projectTemplate" default="init" basedir=".">
<target name="init" depends="init.variables">
<property name="lib.dir" value="lib"/>
<echo message="Hello ${user.name}! lib.dir is set to ${lib.dir}" />
<echo message="build.dir is set to ${build.dir} in build-abstract.xml " >
</echo>
</target>
<target name=" clean" depends="init" >
<del dir="${build.dir}"/>
</target>
&build-abstract;
</project>
|
其中 <!ENTITY build-abstract SYSTEM "file:./build-abstract.xml">
定義了一個名為 build-abstract
的實體,其內容為當前目錄下的 build-abstract.xml
文件。 &build-abstract;
引用了這個實體,這樣在 build.xml
文件就可以用 build-abstract.xml
定義的目標啦。
下面是 build-abstract.xml
的內容:
<target name="init.variables">
<property name="build.dir" value="tempbuild"/>
</target>
|
- 開發和定義自己的task
ant是一個可以擴充的構建工具,開發者可以開發自己的java類來擴充ant。下面是一個簡單的擴充類:
package com.mydomain;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class MyVeryOwnTask extends Task {
private String msg;
// The method executing the task
public void execute() throws BuildException {
System.out.println(msg);
}
// The setter for the "message" attribute
public void setMessage(String msg) {
this.msg = msg;
}
}
|
這個擴展任務將有一個屬性message,ant在執行這個任務時會調用execute方法。下面是在build.xml配置文件中使用這個擴展的示例:
<?xml version="1.0"?>
<project name="OwnTaskExample" default="main" basedir=".">
<taskdef name="mytask" classname="com.mydomain.MyVeryOwnTask">
<classpath>
<pathelement location="where/u/put/the/class/"/>
</classpath>
<target name="main">
<mytask message="Hello World! MyVeryOwnTask works!"/>
</target>
</project>
|
- 目標依賴性
了解ant的另外一點是target的依賴性,上面這個比較復雜一點的build.xml的依賴性如下圖所示:
這樣的依賴圖使得執行命令ant init 時先執行init.variable目標中的指令,執行clean目標時先執行依次執行init.variables和init目標。
到目前為止,還沒有哪一個集成工具開發出自動分析ant配置文件依賴性圖的插件,但是命令行下已經有了。
這個工具名叫 vizant
,也就是一個實現了擴展ant任務的jar文件,還包含了一些文檔和例子,下面是我產生上面的目標依賴圖的 build.xml
<?xml version="1.0"?>
<!-- $Id: build.xml,v 1.1 2003/04/29 10:25:12 gongys Exp $ -->
<project name="Vizant" basedir="." default="dot">
<property name="build" location="output"/>
<property name="vizant.antfile" value="${buildfile}"/>
<property name="dot.format" value="png"/>
<target name="init">
<echo message="${vizant.antfile}" />
<tstamp/>
<mkdir dir="${build}"/>
</target>
<target name="defvizant">
<taskdef name="vizant" classname="net.sourceforge.vizant.Vizant" classpath="vizant.jar"/>
</target>
<target name="vizant" depends="defvizant,init">
<vizant antfile="${vizant.antfile}" outfile="${build}/build.dot" uniqueref="true"/>
</target>
<target name="dot" depends="vizant">
<exec executable="${basedir}/dot.exe" v
<arg line="-T${dot.format} ${build}/build.dot -o ${build}/out.${dot.format}"/>
</exec>
</target>
</project>
|
你在要分析的項目目錄下執行如下命令便可在output/out.png的依賴圖形文件。
gongys$ ant -f vizant/build.xml -Dbuildfile=build.xml
|
-f vizant/build.xml
定義了ant配置文件, -Dbuildfile=build.xml
定義了要分析的ant配置文件。
- Ant配置腳本的面向對象性
從上面可以知道一個ant的配置腳本可以由多個配置文件組成,一個配置文件由目標和屬性定義語句組成。我們可以把屬性看成是面向對象中的成員變量,目標看成是方法,這樣一個配置文件就定義了一個"類",而且它的成員都是靜態的,就是說不需要生成"對象"。一個類是可以運行的如果它的配置文件的頂級元素是<project>,這就好像我們的java類實現了 public static void main(String[] args)
方法一樣。可以用xml中的定義和引用實體的方式來申明一個"類"繼承了另一個"類",這樣我們可以實現面向對象當中的"類繼承層次圖";我們可以用<ant>任務來實現跨對象之間的調用(要求這些對象的類是可以運行的),這樣就形成了"對象協作圖";我們可以用<antcall>和目標的depends屬性來實現對象內部的"方法調用"。
注意Ant配置腳本的面向對象模型沒辦法實現方法重載或覆蓋。
1.2 junit單元測試
大部分集成工具都集成了junit單元測試插件,并有向導幫助寫單元測試。Junit發行包的文檔很詳細地介紹了Junit的設計概念和所使用的設計模式。在這里我簡單地說明如何寫測試用例、在ant配置文件中調用測試用例和產生測試報告的方法。
- 寫測試用例
下面是在eclipse junit向導對MyCode類編寫的測試用例TestMyCode文件基礎上寫的代碼:
import junit.framework.TestCase;
/*
* Created on 2003-4-30
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
/**
* @author gongys
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class TestMyCode extends TestCase {
MyCode myFixture=null;
/**
* Constructor for TestTest.
* @param arg0
*/
public TestTest(String arg0) {
super(arg0);
}
/*
* @see TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
myFixture = new MyCode();
System.out.println("setup");
}
/*
* @see TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
myFixture = null;
System.out.println("teardown");
}
public void testSetName() {
myFixture.setName("gongys")
assertEquals("gongys", myFixture.getName());
System.out.println("testSetName");
System.out.println(this.getName());
}
public void testSetAge() {
System.out.println("testSetAge");
myFixture.setAge (12)
assertEquals(12,myFixture.getAge());
System.out.println(this.getName());
}
}
|
有幾點需要特殊指出:
- 一個TestCase子類中可以包含多個test方法,test方法的原型必須是public void testXXX();
- 在執行過程中,junit框架為每一個test方法實例化一個TestCase子類;
- 執行testCase的順序如下:setUp(),testXXX(),teardown();
- fixture是指為每一個測試方法準備的東西:比如數據庫連接,此時的目標等,一般在setUp()中設置,testXXX()中使用,teardown()中釋放。
運行這個測試的結果如下:
setup
testSetName
testSetName
teardown
setup
testSetAge
testSetAge
teardown
|
- ant使用測試用例
利用ant 的junit任務和其子任務test可以在ant配置文件中執行單元測試,如下所示:
<target name="outter_unittest" depends="init">
<junit printsummary="yes" fork="yes" haltonfailure="no" >
<classpath>
<fileset dir="${build.dir}">
<include name="TestMyCode.class" />
<include name="MyCode.class" />
</fileset>
<pathelement location="${lib.dir}/${junit.jar}"/>
</classpath>
<formatter type="xml"/>>
<!--this specify the output format of junit -->
<test name="TestMyCode" todir="tempjunit" />>
<!--this will run all testXXX methods of the TestMyCode and generate the output to dir tempjunit ,
the output file is TEST-TestMyCode .xml -->
</junit>
</target>
|
需要注意的是:
- 要正確設置
junit
任務的 classpath
子元素, classpath
至少要包含三樣東西, TestCase
子類比如 TestMyCode
,你測試的代碼的 java
類比如 MyCode
,和 junit.jar
;
- 可以使用
formatter
子元素設置 junit
任務中 test
任務的輸出的格式;
test
任務可以設置輸出文件的名字和目錄;
- junit任務還有一個子任務batchtest可以用通配符來指定TestCase子類。
- Ant中生成測試報告
在上面的一節中我們談到junit任務可以生成測試結果,并輸出到指定的文件和目錄中,在ant中,我們還可以用junitreport任務對這些測試結果進行處理,生成html文件:
<junitreport todir="./tempjunit ">
<fileset dir="./tempjunit ">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="./report/html"/>
</junitreport>
|
junitreport任務首先把fileset中指定的測試結果歸集成一個xml文件,接著用子任務report轉化成html文件,子任務report的format屬性指定生成的結果是框架的還是沒框架的。

 |

|
1.3 cactus單元測試
cactus單元測試工具是對junit框架的擴充,使junit的思想和便利同樣用于Browser/Server web應用程序中的測試,具體的來說就是測試servlet,jsp和filter。本節講述cactus 單元測試原理,servlet測試用例的書寫(jsp,filter的測試用例的書寫請參照cactus文檔),如何配置ant運行這樣的測試。
-
cactus 單元測試原理
Cactus提供了好幾個擴展 JUnit Testcase
的子類和相應的redirector,上面的工作原理圖解釋了cactus測試的工作原理。
其中 YYYTestCase = ( ServletTestCase
子類 | FilterTestCase
子類 | JspTestCase
子類)
XXX我們寫的testcase名字的后半部分。
下面我們分步驟解釋在我們的 cactus Testcase
子類里頭的每一個 testXXX()
方法的具體情況:
- JUnit 測試運行器調用
YYYTestCase.runTest()
方法。這個方法尋找 beginXXX(WebRequest)
方法,如果找到則執行。 傳給 beginXXX(WebRequest)
方法的參數 WebRequest
可用來設置 HTTP頭, HTTP 參數,這些參數將被發送到第2步的 Redirector
代理。
YYYTestCase.runTest()
方法打開連向 Redirector
代理的HTTP 連接, beginXXX(WebRequest)
方法設置的HTTP協議參數將被送到代理。
Redirector
代理在服務端作為 YYYTestCase
的代理(其實我們的YYYTestCase被實例化兩次,一次在客戶端被JUnit 測試運行器實例化,一次在服務器端被代理實例化,客戶端實例執行 beginXXX() and endXXX()
方法,服務端實例執行Junit 測試用例的方法 setup(),testXXX(),and teardown())
。Redirector 代理有下列事情可做:
- 用java的內省功能創建服務端實例;
- 設置一些缺省對象;
- 按照客戶端實例的意愿創建session。
- 執行Junit 測試用例的方法
setup(),testXXX(),and teardown();
- 我們的 testXXX()方法調用服務端代碼來進行測試,使用
assertEquals()
方法對測試結果和預期結果進行比較,如果兩者相符為測試成功,否則為測試失敗;
- 如果測試失敗,Redirector 代理將捕獲testXXX()方法拋出的的異常;
- Redirector 代理將異常信息返回給客戶端的JUnit 測試運行器,JUnit 測試運行器可以生成測試報告;
- 如果沒有異常出現,
YYYTestCase.runTest()
方法尋找
endXXX(org.apache.cactus.WebResponse)
endXXX(com.meterware.httpunit.WebResponse)
(后者用在和httpunit集成中) 方法,如果找到則執行。endXXX方法中,我們可以檢查返回的HTTP 頭, Cookies 和output stream ,這個檢查可以借助于Junit的 assertEquals或者cactus提供的幫助類。
在這里需要提出的一點就是:代理不會去真正執行servlet,或filter,或jsp的代碼,你需要在testXXX方法中調用或模仿這些代碼。
-
書寫servlet測試用例
import java.io.IOException;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;
import org.apache.cactus.WebResponse;
public class TestSampleServlet extends ServletTestCase
{
public TestSampleServlet(String theName)
{
super(theName);
}
public static Test suite()
{
return new TestSuite(TestSampleServlet.class);
}
//這個方法在服務端運行,用來設置fixture
public void setup(){
}
//這個方法在服務端運行,用來釋放fixture
public void teardown(){
}
//這個方法在客戶端運行,可以用來設置請求參數
public void beginSaveToSessionOK(WebRequest webRequest)
{
webRequest.addParameter("testparam", "it works!");
webRequest.setURL("localhost", "test", "SampleServlet" ,"gongys", "name=gongys");
}
//這個方法在服務端運行,用來具體進行代碼測試
public void testSaveToSessionOK() throws IOException
{
SampleServlet servlet = new SampleServlet();
servlet.saveToSession(request);
System.out.println(this.request.getPathInfo());
System.out.println(this.request.getParameter("name"));
this.response.getWriter().println("gongys");
assertEquals("it works!", session.getAttribute("testAttribute"));
}
//這個方法在客戶端執行,用來驗證返回結果
public void endSaveToSessionOK(WebResponse theResponse){
System.out.println(theResponse.getText());
}
}
|
-
配置ant運行cactus測試
- 類路徑的設置
我們要按照下面的圖設置客戶端(ant junit任務中)設置classpath,并把右半部分所示的類放到服務器或者webapp的類路徑上
- 客戶端cactus.properties
我們知道,cactus需要redirector 代理才能工作,我們除了把這些代理考到相應的webapp的類路徑(對于filter和servlet代理)或webapp路徑(對于jsp代理)外,我們還需要告訴客戶端測試實例到哪里去找這些代理,下面是cactus.properties的內容:
cactus.contextURL = http://localhost:8080/test
|
其中test為被測試webapp的上下文路徑。
cactus.properties也必須放在ant junit任務的classpath中。
- 服務器(假設為tomcat 4.12)server.xml的設置
我們必須在server.xml中添加cactus redirector代理,使得這些代理能接受客戶端測試實例傳過來的請求。詳細添加辦法請參見cactus 文檔。
有了正確的junit 類路徑的設置,其他的就合正常的junit測試一樣。
1.4 clover測試覆蓋率計算
clover覆蓋率計算工具通過在被測源代碼中插入相關指令,在被測源代碼被執行時這些指令被執行,用以統計被測源代碼被執行的次數,clover利用一個數據庫來保存這些數據。Clover還提供了訪問這個數據庫的工具,并產生html報告文檔。
-
配置ant運行clover分析
clover實現了一些ant任務,下面是ant中定義這些任務的代碼
<taskdef resource="clovertasks" >
<classpath>
<pathelement location="${clover.jar}"/>
</classpath>
</taskdef>
|
下面的代碼初始化clover數據庫:
<target name="with.clover" depends="init">
<!-- 刪除${build.dir}使得重新編譯源代碼 -->
<delete dir="${build.dir}" />
<mkdir dir="${build.dir}" />
<clover-setup initString="${user.home}/${ANTLOG_FILE_NOEXT}.db" />
</target>
|
下面的代碼產生clover分析,格式為html,結果放在tempcloverreport目錄中:
<target name="clover.html" >
<delete dir="tempcloverreport"></delete>
<mkdir dir="tempcloverreport" />
<property name="clover.html" value="ok"<>/property>
<clover-report>
<current outfile="tempcloverreport">
<format type="html"/>
</current>
</clover-report>
</target>
<!-- 下面用一個目標來初始化clover,編譯源代碼,unittest單元測試和clover分析-->
<target name="clover_report" depends="with.clover, compile,unittest, clover.html">
</target>
|
這個任務的工作原理為,with.clover在初始化clover數據庫后,監視compile;在javac編譯java源代碼時把記錄代碼執行的相關指令插入到java源代碼中;在單元測試時,這些插入的代碼就開始記錄被測試代碼的執行次數,把結果輸出到clover數據庫中;clover.html目標根據數據庫中的數據生成html文件。
需要注意的幾點:
- 如果是執行cactus類的client/server測試,在服務端的類徑中必須包含clover.jar類;
- clover 是一個商業工具,但可以得到30天的評估license;
- clover 在編譯過程中改變了代碼的執行路徑,在產品發布時必須單獨執行compile目標。
- Clover 分析結果
下面是Clover 分析結果的圖示,讀者可以自己看出從這個分析中能得到什么。第一個圖是顯示一個項目的整體覆蓋率情況,第二個圖顯示了每一個類每行代碼的覆蓋情況。
1.5 statcvs項目度量工具
statcvs是一個利用cvs reporsitory log生成項目度量的工具,這些度量包括每個作者的代碼量,每個目錄或文件的代碼行數。使用statcvs先要學會使用cvs。
- Ant 中使用cvs
Ant 中使用cvs是通過cvs任務來完成的:
<property name="cvsroot" value=":pserver:anonymous@10.1.36.135:/data/src" />
<!--取出源代碼,放在tmp目錄下,相當于執行cvs co -d ${base.path}/tmp/${location} -->
<cvs cvsRoot="${cvsroot}"
package="${location}"
dest="${base.path}/tmp"
/>
<!-- 執行cvs log ,結果放在tmp.log中-->
<cvs dest="${base.path}/tmp/${location}" command="log" output="${base.path}/tmp/${location}/cvs.log"/>
|
- Ant 中使用statcvs
Statcvs實現了一個ant任務,下面是ant中定義這個任務的代碼:
<taskdef name="statcvs" classname="net.sf.statcvs.ant.StatCvsTask">
<classpath>
<pathelement path="${statcvs.jar}"/>
</classpath>
<</taskdef>
|
下面是使用statcvs任務產生項目度量數據的代碼,結果是一些html文件,放在${statcvs.htmldir}目錄下:
<statcvs
projectName="${location}"
projectDirectory="${base.path}/tmp/${location}"
cvsLogFile="${base.path}/tmp/${location}/cvs.log"
outputDirectory="${statcvs.htmldir}"
/>
|
1.6 velocity模版系統
velocity模版系統比起jsp模版來說有比較大的好處:
- 實現視圖和控制代碼的完全隔離
在jsp中,我們可以嵌入執行代碼,jsp本質是具有格式化代碼和控制代碼混合能力,雖然大家發明了好多方法、設計模式和最佳實踐,可是不能從根本上消除jsp編寫員混合格式化代碼和控制代碼的惡習;而在velocity模版系統中,這種混合不可能存在,你不可能在velocity的.vm文件中通過代碼 Person p = new Person()
生成一個Java對象,這些業務對象只能在控制中生成并放到context中。
- 安全
jsp文件被編譯之后形成了一個類似于servlet的東西,幾乎可以在jsp中干任何事,你可以在jsp中寫 System.exit(0)來關掉java虛擬機,或利用別的什么漏洞。
這里只說這些好處,關于其他的大家可以到網上去查或自己總結。下面我要介紹一下velocity模版系統工作機制和關于velocity的設置問題。
- velocity模版系統工作機制
我們以在servlet環境下的模版系統為例(當然控制還可以由其他代碼來實現)。控制可以實例化一些業務對象比如Person 放到context 中(執行context的相關方法),控制在接著裝載相關的視圖的模版比如PersonInfo.vm,產生Template實例,并讓這個實例解釋自己生成輸出比如html格式流,Template實例在解釋模版的時候會根據模版文件中的指令訪問context中的業務對象。
所以要使這個模式工作,重要的一點是控制必須和視圖就context中的業務對象的名字達成一致,這就是控制和視圖的協議。
- velocity的設置
velocity運行的第一個任務就是初始化,執行Velocity.init方法。無參數的init方法會采用缺省的屬性配置,在velocity.jar 中的org.apache.velocity.runtime.defaults.velocity.properties位置;使用有參數的init方法,參數傳遞的是一個屬性文件或java.util.Properties 對象,參數中定義的屬性會覆蓋缺省的屬性設置,沒定義的屬性會采用缺省的屬性設置。
比較有用的屬性設置是讀取模版文件時采用的字符集、產生輸出流時使用的編碼、模版所在的位置和模版裝載器:
input.encoding = gbk
output.encoding = gbk
file.resource.loader.path = templates
|
2、文檔書寫輔助工具
- word 文檔書寫排版工具
- powerpoint,圖片組織繪畫工具
- visio 繪制數據流圖,ER圖等的工具
- rational rose,繪制UML圖形的工具
- windows 附件中的畫圖來截取圖片
- 操作系統的全屏打印功能
本文是實戰每晚構建系列的第三篇,利用第二篇文章中敘述的開源技術對第一篇中的分析模型進行設計和實現。
1、構建信息顯示系統的設計
這是一個典型的web應用系統,不過非常簡單。根據《面向對象的系統分析和設計》所描述的,設計主要對四個部分進行描述:
- 問題域的細化:考慮將來實現語言的特性和利用某些設計模式,對分析模型進行細化,并作某些權衡。實現對未來系統"如何做事情"的描述。
- 人機界面設計:考慮和使用者的交互,對信息顯示的布局和接收用戶指令或數據的行為進行設計。
- 存儲設計:考慮如何保存業務數據,主要考慮現在的存儲方式,同時也要考慮未來存儲方式的可能變化,即業務對象和存儲方式的耦合性問題。
- 系統接口設計:主要考慮本系統和外在系統之間的合作協議,特別是不要忘了和操作系統或驅動程序的接口。
1.1 分析模型的細化
我們在分析模型中沒有考慮實現的問題,現在我們必須細化構建信息顯示系統的分析模型,使之達到可實現之地步。
根據前面velocity模版系統地介紹,我們擴充一下顯示系統的類圖。如下面的"顯示系統設計類圖"可知,由于Java語言不支持多繼承,而且原來分析模型的BuildInfoLog類只有成員變量,我們把這個類改變成Java語言當中的接口來實現,我們把NightlyBuild變成了實現這個接口的一個類,因此同樣擁有BuildInfoLog的數據成員。另外NightlyBuild還繼承了Java.util.Hashtable類,使得其表現為一個哈西表,這個哈西表以項目的名字為Key,以該項目的構建標簽列表為值。根據MVC模式的精神,NightlyBuild類是個業務對象類,應該從信息顯示的格式化功能中解放出來,所以NightlyBuild類原來的list_ Buildinfo_table方法放到buildinfo_list.vm模版文件中實現。同時我們增加了一個NightlyBuildServlet類來響應構建信息關心者的訪問需求,它是velocity中VelocityServlet類的子類,因此NightlyBuildServlet任MVC模式中的控制角色。
在下面的"顯示系統MVC視圖"中可以看出,業務對象NightlyBuild對象以nightlyBuild的名字保存在模型context中,當控制NightlybuildServlet用模版文件buildinfo_list.vm來滿足構建信息關心者瀏覽構建信息目錄的要求時,buildinfo_list.vm文件格式化保存在context中的NightlyBuild對象的信息并形成視圖供瀏覽者瀏覽。
1.2 人機界面設計
人機界面設計就是設計瀏覽者和系統交互的方式,以及系統展示信息的布局。構建信息顯示系統是個典型的web應用,瀏覽者首先在其瀏覽器中輸入構建信息顯示系統的URL,系統就會把構建信息目錄顯示給瀏覽者,瀏覽者通過點擊構建信息目錄頁面上的超鏈就可以訪問相應的信息。我們前面說過每個構建信息會在瀏覽器中自己顯示自己,所以現在的任務就是設計構建信息目錄顯示頁面。
下面的圖就是構建信息目錄顯示頁面,其中${key}表示某個項目的名字,${nightly_build_tag}為某個項目某次構建的標簽。
管理日志超鏈的url為${logTopDir}/
構建日志超鏈的url為${projectLogTopDir}/${nightly_build_tag}.txt
度量信息超鏈的url為${logTopDir}/${nightly_build_tag}
測試記錄超鏈的url為${testTopDir}/${nightly_build_tag}
覆蓋率超鏈的url為${testCoverTopDir}/${nightly_build_tag}
產品超鏈的url為${distTopDir}/${nightly_build_tag}
每晚構建平臺 |
|
${key} |
構建序列 |
構建日志 |
構建日志 |
度量信息 |
測試記錄 |
覆蓋率 |
產品 |
1 |
管理日志 |
構建日志 |
度量信息 |
測試記錄 |
覆蓋率 |
產品 |
2 |
|
|
|
|
|
|
... |
|
|
|
|
|
|
${key} |
構建序列 |
構建日志 |
構建日志 |
度量信息 |
測試記錄 |
覆蓋率 |
產品 |
1 |
管理日志 |
構建日志 |
度量信息 |
測試記錄 |
覆蓋率 |
產品 |
2 |
|
|
|
|
|
|
... |
|
|
|
|
|
|
1.3 存儲設計
存儲設計主要是指數據庫的設計或者文件格式的設計,構建信息顯示系統使用文件系統作為存儲目標。構建信息顯示系統的存儲設計主要工作是定義分析模型中BuildInfoDir類也就是設計模型中BuildInfoDir接口的變量的值和格式。
1.3.1 BuildInfoDir接口
類名/接口名 |
構建信息存放位置接口 |
類英文名 |
BuildInfoDir |
成員變量 |
變量名 |
變量說明 |
缺省值或值 |
webAppDir |
是應用服務器存放web應用程序的根目錄,這個值根據系統安裝而定。 |
|
nightlyWebAppName |
構建信息顯示系統web應用程序的名字,同變量webAppDir一起構成了構建信息顯示系統應用程序的根目錄。 |
"nightlybuild" |
nightly_Build_Tags |
保存所有的構建標簽,是構建信息顯示系統web應用程序根目錄下的一個文件的名字。 |
"nightBuildLog" |
logTopDir |
相對于顯示系統應用程序的根目錄下的一個目錄名,保存構建管理服務運行的日志的目錄 |
"adminLogs" |
statCVSTopDir |
相對于顯示系統應用程序的根目錄下的一個目錄名,保存了所有應用項目的項目度量結果的頂層路徑 |
"statCVSes" |
projectLogTopDir |
相對于顯示系統應用程序的根目錄下的一個目錄名,保存了所有應用項目構建服務實例運行日志的頂層路徑 |
"projectLogs" |
testCoverTopDir |
相對于顯示系統應用程序的根目錄下的一個目錄名,保存了所有應用項目的測試覆蓋率計算結果的頂層路徑 |
"testCovers" |
distTopDir |
保存了所有應用項目的發布版的頂層路徑,值和變量webAppDir相同 |
|
testTopDir |
相對于顯示系統應用程序的根目錄下的一個目錄名,保存了所有應用項目的測試結果的頂層路徑 |
"tests" |
下面是nightly_Build_Tags所指的文件格式:
每行包括一個項目構建標簽,構建標簽的格式為項目名-yyyymmdd_HHMMSS的格式
比如可能的文件內容如下:
nightlybuild-20030312_080100
cover-20030312_080100
nightlybuild -20030312_080100
cover-20030313_080100
|
這個文件內容表示:兩個項目分別為nightlybuild和cover,他們在2003年3月12日和13日的早上8點1分得到了構建。
1.4 系統接口設計
系統接口設計主要描述被設計的系統與外界系統特別是操作系統的接口的設計和描述。構建信息顯示系統和構建系統的接口就是存儲設計中的BuildInfoDir接口,和操作系統無接口。
2、構建系統的設計
2.1 分析模型設計
考慮到ant腳本的局限性,我們對構建系統分析模型作如下調整:
因為OSScheduler是個操作系統定時服務的腳本或配置,所以無法繼承BuildInfoLog類,我們增加一個LogAdmin類用來啟動和記錄BuildAdmin運行的日志。為什么我們必須在OSScheduler和BuildAdmin中插入一個LogAdmin類?這是因為ant腳本無法自己記錄自己的輸出,必須靠調用者,我們必須使用<ant>任務來運行和記錄另外一個類的輸出。同樣BuildAdmin會用<ant>任務來運行和記錄ProjectBuild的輸出日志。
LogAdmin用<ant>任務實現對BuildAdmin的單項聯系;至于BuildAdmin和ProjectBuild之間的一對多的關系可用多個<ant>任務實現。由于要運行,所以類LogAdmin,BuildAdmin和ProjectBuild都必須是可運行的(runnable)。
一個項目的構建應該可以分成固定不變的腳本和項目相關腳本兩部分,這個需求可以和設計模式中的模版方法模式的目的相吻合。
類ProjectBuild的test_project方法要完成下列步驟:
- 準備測試環境,包括為測試目的而進行的編譯、打包、安裝,其中編譯和打包與具體項目有關;
- 測試,這個任務與具體項目有關;
- 生成測試報告,它與項目無關;
- 生成覆蓋率信息,它也與項目無關。
類ProjectBuild的dist_project方法要完成的步驟有編譯、打包和安裝,安裝與項目無關。這樣運用了模版方法模式之后,構建系統的設計類圖就變成了構建系統設計類圖二所示。
下面的序列圖顯示了模版方法調用抽象方法的序列:
步驟1.1針對每個被管理的應用項目調用步驟1.1.1;
步驟1.1.1.4.1 test_project是個模版方法,compile(),compiletestcases(),test_war(),test()是子類ProjectBuild要實現的方法;
步驟1.1.1.4.2 dist_project是個模版方法,compile(),dist_war()是子類ProjectBuild要實現的方法。
2.2 人機界面設計
不需要。
2.3 存儲設計
見構建信息顯示系統。
2.4 系統接口設計
構建系統和操作系統的接口在OSScheduler。在Linux下可以實現成一個調用ant LogAdmin的shell 可執行文件,并配置crond每晚某個時刻執行這個可執行文件。
3、實現
在這節中充分利用本文章系列中篇中所有的技術,并顯示了部分源代碼。
3.1 部署圖
在實現時,第一個要考慮的就是類如何與源文件對應,這些源文件又是如何組織的,表示這些信息的圖表稱為部署圖。圖表的格式不一定要很標準,這要能表達意思就行。
從每晚構建部署圖可以看出,這些類被分別組織在兩個不同的目錄下:work_nightly和work_nightlybuild。work_nightly目錄存放的是跨項目的構建信息,稱為每晚構建平臺構建系統中的構建管理子系統,除了包括實現BuildAdmin,BuildInfoLog和LogAdmin的源代碼外,還有應用服務器目錄Tomcat412,編譯和測試程序時常用jar類庫目錄lib,測試b/s架構的程序的配置信息目錄cactusconf和生成ant配置文件依賴性圖解的vizant目錄。work_nightlybuild是一個支持每晚構建項目的目錄,在這里是構建信息顯示系統項目,這個目錄包括了類ProjectBuild和ProjectBuildAbstract的源代碼,同時還具有一個web項目該有的的文件和目錄。值得指出的是為了不至于有多份實現BuildInfoLog類的源代碼,在具體項目中包括了一個指向構建管理子系統頂級目錄的文件稱為指針文件。類ProjectBuild、ProjectBuildAbstract和指針文件組成了構建系統中的項目構建子系統。
3.2 存儲接口BuildInfoLog
文件BuildInfoLog.properties實現存儲接口,內容如下:
###=================================###
logTopDir= "adminLogs"
statCVSTopDir="statCVSes"
testTopDir="tests"
distTopDir="/usr/tomcat412/webapp/"
nightly_Build_Tags=" nightBuildLog"
projectLogTopDir="projectLogs"
testCoverTopDir="testCovers"
webAppDir="/usr/tomcat412/webapp/"
nightlyWebAppName="nightlybuild"
###=============================###
|
這個文件實現的目錄結構如下,這個目錄結構在構建信息顯示系統的web應用程序中創建。
3.3 類LogAdmin
<project name="BuildAdminSystem" default="execute" basedir="." >
<!-- 定義方法 -->
<target name="init" >
<tstamp>
<format property="DSTAMP" pattern="yyyyMMdd_HHmmss" locale="cn"/>
</tstamp>
<!-- 繼承BuildInfoLog -->
<property file="BuildInfoLog.properties"/>
</target>
<target name="execute" depends="init" >
<echo>
###########################
LogAdmin.xml:target execute
the BuildAdmin.xml's output will put into file:
${webAppDir}${nightlyWebAppName}/${logTopDir}/${logNamePrefix}-${DSTAMP}.txt
###########################
</echo>
<!-- 跨類的方法調用ant task -->
<ant antfile="BuildAdmin.xml"
inheritAll="false"
output="${webAppDir}${nightlyWebAppName}/${logTopDir}/${logNamePrefix}-${DSTAMP}.txt" >
<!-- 傳遞參數 -->
<property name="tagTime" value="${DSTAMP}"/>
</ant>
</target>
</project>
|
3.4 類BuildAdmin
<project name="BuildAdminSystem" default="execute" basedir="." >
<!-- 定義方法 -->
<target name="init" >
<!-- 繼承BuildInfoLog -->
<property file="BuildInfoLog.properties"/>
<property name="cvsroot" value=":pserver:anonymous@10.1.36.135:/data/src" />
<property name="buildtemp.dir" value="../buildtemp" />
<property name="lib.dir" location="lib" />
<property name="AJPPORT" value="9887" />
<property name="HTTPPORT" value="9888" />
<property name="HTTPSPORT" value="9889" />
<property name="statcvs.jar"
location="${lib.dir}/statcvs-0.1.3.jar"/>
<taskdef name="statcvs" classname="net.sf.statcvs.ant.StatCvsTask">
<classpath>
<pathelement path="${statcvs.jar}"/>
</classpath>
</taskdef>
</target>
<target name="execute" >
<!-- you can add another module here -->
<antcall target="build_One_Module">
<param name="module" value="nightlybuild"/>
</antcall>
<!-- you can add another module here -->
<!--
<antcall target="build_One_Module">
<param name="module" value="testcvs"/>
</antcall>
-->
</target>
<!-- 方法調用之一depends -->
<target name="build_One_Module" depends="init">
<!-- 方法調用之一antcall -->
<antcall target="cvs_Check_Out">
<!-- the CVS module name which includes a build.xml itself -->
<!-- 傳遞參數 -->
<param name="module" value="${module}"/>
</antcall>
<!-- 生成構建標簽 -->
<property name="nightly_Build_Tag" value="${module}-${tagTime}" />
<echo>
###############################################
BuildAdmin.xml:target build_One_Module:
nightly_Build_Tag is set to ${nightly_Build_Tag}
###############################################
</echo>
<echo>
###############################################
BuildAdmin.xml:target build_One_Module:
append ${nightly_Build_Tag} to file
${webAppDir}/${nightlyWebAppName}/${nightly_Build_Tags}
###############################################
</echo>
<!-- 記錄構建標簽 -->
<echo append="true" file="${webAppDir}/${nightlyWebAppName}/${nightly_Build_Tags}" >
${nightly_Build_Tag}
</echo>
<!-- 生成項目度量信息 -->
<antcall target="statCVS">
<param name="module" value="${module}"/>
<param name="build_tag" value="${nightly_Build_Tag}" />
</antcall>
<echo>
###############################################
BuildAdmin.xml:target build_One_Module:
the software is checked out in ${buildtemp.dir}/tmp/${module}
let's begin to build it and the output will be put into
${webAppDir}${nightlyWebAppName}/${projectLogTopDir}/${nightly_Build_Tag}.txt
###############################################
</echo>
<ant dir="${buildtemp.dir}/tmp/${module}"
target="execute"
inheritAll="false"
output="${webAppDir}${nightlyWebAppName}/${projectLogTopDir}/${nightly_Build_Tag}.txt" >
<property name="nightly_Build_Tag" value="${nightly_Build_Tag}" />
<property name="AJPPORT" value="${AJPPORT}" />
<property name="HTTPPORT" value="${HTTPPORT}" />
<property name="HTTPSPORT" value="${HTTPSPORT}" />
</ant>
<!--
<ant dir="../work_nightlybuild"
antfile="ProjectBuild.xml"
target="execute"
inheritAll="false"
output="${webAppDir}${nightlyWebAppName}/${projectLogTopDir}/${nightly_Build_Tag}.txt" >
<property name="nightly_Build_Tag" value="${nightly_Build_Tag}" />
<property name="AJPPORT" value="${AJPPORT}" />
<property name="HTTPPORT" value="${HTTPPORT}" />
<property name="HTTPSPORT" value="${HTTPSPORT}" />
</ant>
-->
</target>
<target name="cvs_Check_Out" >
<!-- cvs checkout -->
<echo>
###############################################
BuildAdmin.xml:target cvs_Check_Out:
the software will be checked out in ${buildtemp.dir}/tmp/${module}
###############################################
</echo>
<delete dir="${buildtemp.dir}/tmp/${module}" />
<mkdir dir="${buildtemp.dir}/tmp"/>
<cvs cvsRoot="${cvsroot}"
package="${module}"
dest="${buildtemp.dir}/tmp"
/>
<cvs dest="${buildtemp.dir}/tmp/${module}" command="log" output="${buildtemp.dir}/tmp/${module}/cvs.log"/>
</target>
<!-- 生成項目度量信息 -->
<target name="statCVS" depends="init" >
<echo>
###############################################
BuildAdmin.xml:target statCVS:
the software will be measureed in ${webAppDir}${nightlyWebAppName}/${statCVSTopDir}/${build_tag}
###############################################
</echo>
<delete dir="${webAppDir}${nightlyWebAppName}/${statCVSTopDir}/${build_tag}" />
<mkdir dir="${webAppDir}${nightlyWebAppName}/${statCVSTopDir}/${build_tag}"/>
<statcvs
projectName="${module}"
projectDirectory="${buildtemp.dir}/tmp/${module}"
cvsLogFile="${buildtemp.dir}/tmp/${location}/cvs.log"
outputDirectory="${webAppDir}${nightlyWebAppName}/${statCVSTopDir}/${build_tag}"
/>
</target>
</project>
|
3.5 顯示界面模版
顯示界面模版在velocity模版文件buildinfo_list.vm中實現。這個文件引用了nightlyfrag.vm文件,另外定義宏headerCell的文在為GlobalMacros.vm。
模版文件buildinfo_list.vm:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=GB2312">
<title>每晚構建</title>
<link href="css/default.css" type="text/css" rel="stylesheet">
</head>
<body bgcolor="#ffffff">
<center>
<table border="2" cellspacing="0" cellpadding="3" bordercolor="#000000">
<tr>
<td class="page-title" bordercolor="#000000" align="left" nowrap>
<font size="+2"><b> 每晚構建 </b> </font>
</td>
</tr>
</table>
<p/>
<table border="1" cellspacing="0" cellpadding="3">
#parse( "nightlyfrag.vm" )
</table>
</center>
</html>
|
模版文件nightlyfrag.vm:
#headerCell("header-center" "序號")
#headerCell("header-left" "管理日志" )
#headerCell("header-left" "構建日志" )
#headerCell("header-left" "度量信息" )
#headerCell("header-left" "測試記錄" )
#headerCell("header-left" "覆蓋率" )
#headerCell("header-left" "產品" )
<ul>
#foreach( $key in $BuildList.keySet() )
<tr>
<td colspan="10" class="title">$key</td>
</tr>
#set($body=$BuildList.get($key))
#foreach($entry in $body)
#set($tagtime=$BuildList.getTagTime($entry))
<tr>
<td class="row-center">$velocityCount </td>
<td class="row-left"><a href="${BuildList.LogTopDir}/${BuildList.logNamePrefix}${tagtime}.txt" >管理日志</a></td>
<td class="row-left"><a href="${BuildList.projectLogTopDir}/${entry}.txt" >構建日志</a></td>
<td class="row-left"><a href="${BuildList.statCVSTopDir}/${entry}" >度量信息</a></td>
<td class="row-left"><a href="${BuildList.testTopDir}/${entry}" >測試記錄</a></td>
<td class="row-left"><a href="${BuildList.testCoverTopDir}/${entry}" >覆蓋率</a></td>
<td class="row-left"><a href="/${entry}" >產品</a></td>
</tr>
#end
#end
</ul>
|
宏定義文件GlobalMacros.vm:
#macro (headerCell $classStyle $body)
<td class="$classStyle">
<b>
$body
</b>
</td>
#end
|
4、結語
到此所有工作結束,我們可以享受自動構建帶來的效益和好處。值得提及的是保持分析、設計和實現文檔的一致性非常困難而且非常重要,工作要不斷地進行反復。編寫良好的文檔,保持優秀的寫作習慣需要單位和個人共同的努力。
5、文檔書寫輔助工具
- word 文檔書寫排版工具
- powerpoint,圖片組織繪畫工具
- visio 繪制數據流圖,ER圖等的工具
- rational rose,繪制UML圖形的工具
- windows 附件中的畫圖來截取圖片
- 操作系統的全屏打印功能
參考資料
- 進一步學習面向對象的系統分析和設計:《面向對象的系統分析和設計》Ronald J. Norman
- 《實用面向對象軟件工程教程》殷人昆 田金蘭 馬曉勤 譯
- 良好的用例編寫風格可以從這里獲得:《編寫有效用例》 Alistair Cockburm
- 進一步理解cvs和nightlybuild技術的相關背景資料:《cvs和nightlybuild技術》 楊錦方
- cvs源代碼版本系統在:http://www.cvshome.org
- statcvs 項目工作量分析工具在:http://statcvs.sf.net/
- clover測試覆蓋率分析工具在: http://www.cortexebusiness.com.au/
- ant構建工具在:http://ant.apache.org
- junit單元測試工具在:http://www.junit.org
- apache web程序測試工具在:http://jakarta.apache.org/cactus/
關于作者
 |
|
 |
龔永生,你可以通過 gongys@legend.com與他聯系。 地址(addr): 北京市海淀區上地信息產業基地開拓路7號聯想大廈 郵編 100085 電話(tel): 010-62986638-5749 手機(mobile): 13910304330 傳真(fax): 010-62975824
|