構(gòu)建理想的模塊自測結(jié)構(gòu)
溫輝敏(wenhm@sina.com)
軟件測試的工作量很大(業(yè)界統(tǒng)計(jì)達(dá)到40% 到60%的總開發(fā)時(shí)間),而又有很大部分適于自動化,因此,測試的改進(jìn)會對整個(gè)開發(fā)工作的質(zhì)量、成本和周期帶來非常顯著的效果。
極限編程(XP)中推薦的自動化單元測試,是指在編寫代碼之前先寫好測試代碼,代碼編到一定階段就用寫好的測試代碼進(jìn)行測試。自動化單元測試可以帶來如下好處:
l 由于測試代碼(測試用例)的不斷增加,這些測試總讓我們寫出的代碼往正確的方向靠攏,使我們不至于重犯以前的錯(cuò)誤。
l 由于可以實(shí)現(xiàn)自動測試,所以可以很好的解決人工很難進(jìn)行的回歸測試問題。
我們沿著“建立測試=>令測試通過=>再建立測試=>再令測試通過”的模式,一步一步地把整個(gè)程序正確地開發(fā)出來。
1.測試代碼的幾個(gè)關(guān)鍵環(huán)節(jié)
對測試代碼進(jìn)行概括性分析,可以發(fā)現(xiàn)測試代碼都具有類似的結(jié)構(gòu),每一個(gè)測試用例的執(zhí)行都要包括測試數(shù)據(jù)初始化、運(yùn)行要測試的代碼、驗(yàn)證測試結(jié)果正確性三部分,見圖1-1。

圖1-1 測試代碼的一般性流程
由于測試代碼一般都包含了圖1-1中的三部分,我們要改善測試代碼使之具有良好的結(jié)構(gòu)只要從這三方面入手即可。
1)將測試數(shù)據(jù)和測試結(jié)果從測試代碼中分離出來
在采用CXXUNIT系列測試工具開發(fā)測試代碼時(shí),發(fā)現(xiàn)一般編程人員都是測試用例和測試代碼混雜在一起,同樣測試結(jié)果也是和測試代碼混雜在一起,這樣就導(dǎo)致測試用例和測試結(jié)果的管理非常困難,因?yàn)橐芾砻總€(gè)用例的數(shù)據(jù)和結(jié)果實(shí)際上就是去管理這些代碼。而且對于一個(gè)函數(shù)(或功能) 每增加新測試用例,就要多出一份類似的代碼,代碼的邏輯實(shí)際上都是一致的,和以前測試代碼的不同點(diǎn)就是在初始化數(shù)據(jù)、測試結(jié)果的不同,這實(shí)際上也導(dǎo)致了代碼的重復(fù)。
我們可以將測試數(shù)據(jù)和測試結(jié)果從測試代碼中分離出來,使得某一個(gè)函數(shù)(或功能)的測試代碼就一份,這一份測試代碼應(yīng)可以進(jìn)行多組測試數(shù)據(jù)的測試,可以進(jìn)行多組測試結(jié)果的驗(yàn)證。
2)將測試數(shù)據(jù)和測試結(jié)果放入文件中,并按目錄存放
將測試數(shù)據(jù)和測試結(jié)果從測試代碼中分離出來是為了更好的管理代碼和測試數(shù)據(jù),將每個(gè)測試用例的數(shù)據(jù)和結(jié)果都放入到一個(gè)文件中,文件名字或文件所在目錄起上能表明測試用例含義的名字,這樣管理起來就方便多了,見圖1-2。

圖1-2 測試用例文件及目錄結(jié)構(gòu)圖
由圖1-2中可以看出此時(shí)測試用例非常直觀,從目錄名就可以知道該目錄下的為那個(gè)功能的測試用例,從測試用例文件名就可以知道這個(gè)測試用例測的哪一個(gè)方面。當(dāng)一個(gè)功能的測試用例非常多可以分成許多類別時(shí)我們還可以在下面再創(chuàng)建測試用例分類子目錄,使得不同類型的測試用例能分隔開方便管理(詳見圖2-1)。
3)將測試數(shù)據(jù)和測試結(jié)果綁定在一起
當(dāng)每個(gè)測試用例對應(yīng)的測試結(jié)果都一樣時(shí)可以將測試結(jié)果嵌入到測試代碼中,當(dāng)不同的測試用例要對應(yīng)不同的測試結(jié)果時(shí),根據(jù)1)中論述應(yīng)該將測試結(jié)果也從測試代碼中分離出來。為了不混淆測試數(shù)據(jù)和測試結(jié)果之間的關(guān)系,將它們放在同一個(gè)文件中。這里舉個(gè)例子,圖1-2中的“閏年2月份的測試用例.ini”文件中內(nèi)容如圖1-3:

圖1-3 測試用例結(jié)構(gòu)圖
由圖1-3可以看到,測試數(shù)據(jù)和測試結(jié)果放在一起,增加了可讀性和維護(hù)性。
4) 一份測試代碼來運(yùn)行多份測試用例
怎樣讓一份測試程序可以進(jìn)行多組數(shù)據(jù)的測試和結(jié)果的比較呢?3)中已經(jīng)將測試用例分門別類,并由相應(yīng)的目錄結(jié)構(gòu)組織起來。此時(shí)測試程序只需每次從測試目錄中取出一個(gè)測試用例文件,進(jìn)行初始化,然后執(zhí)行測試,最后比較測試結(jié)果;測試完一個(gè)用例文件,再取下一個(gè)文件進(jìn)行測試,如此循環(huán)直到所有的用例文件都測了一遍,詳見圖1-4。

圖1-4一份測試代碼測試多個(gè)測試用例的流程圖
2.測試用例管理方案設(shè)計(jì)
若再加上邊界數(shù)據(jù)要測試的數(shù)據(jù)組數(shù)就更多了,一般CXXUNIT系列編寫的測試代碼是每組測試數(shù)據(jù)(其實(shí)一組數(shù)據(jù)就對應(yīng)一個(gè)測試用例)都要編寫初始化代碼,然后調(diào)用相應(yīng)功能函數(shù)測試。這樣導(dǎo)致:
在自動測試的整個(gè)過程中,測試用例的可維護(hù)性會影響到將來測試用例增加的難易度,良好的自測程序應(yīng)能很方便的擴(kuò)充測試用例。
在采用CXXUNIT系列測試工具開發(fā)測試代碼時(shí),對于一些簡單的測試可以測試用例就嵌在測試代碼中。但當(dāng)某一個(gè)功能或函數(shù)要進(jìn)行很多組數(shù)據(jù)(如邊界數(shù)據(jù))的測試時(shí)使用這種方法就得重復(fù)編寫測試代碼,可能每增加一個(gè)測試用例就要編寫大量的重復(fù)測試代碼。
舉例:要測試周期會議預(yù)約功能的代碼,要測試以下幾組數(shù)據(jù):
1)每日召開的周期會議
1.1)按召開次數(shù)預(yù)約的周期會議
1.2)按開始時(shí)間、終止時(shí)間預(yù)約的周期會議
2)每周召開的周期會議
……(內(nèi)容和1.1、1.2一致)
3)每月召開的周期會議
……(內(nèi)容和1.1、1.2一致)
4)每年召開的周期會議
……(內(nèi)容和1.1、1.2一致)
l 隨著測試數(shù)據(jù)組數(shù)的增加,將出現(xiàn)大量做重復(fù)動作的測試代碼,這些測試代碼之間唯一的不同是由于初始化的數(shù)據(jù)不一樣而已。
l 當(dāng)一個(gè)功能只需一兩個(gè)測試用例時(shí),測試用例嵌入在測試代碼中可以進(jìn)行控制和管理,當(dāng)一個(gè)功能測試時(shí)需要大量的測試用例時(shí)(見舉例),大量的測試用例嵌在代碼中將很難管理,你要知道某個(gè)用例是否已經(jīng)有了還得去遍歷測試代碼比較麻煩。
2.1測試用例目錄結(jié)構(gòu)
為此很有必要設(shè)計(jì)一套能良好管理和添加測試用例的體系結(jié)構(gòu)。
當(dāng)一個(gè)功能有很多組測試數(shù)據(jù)時(shí),我們可以將測試用例數(shù)據(jù)全部存放在文件中,使測試用例和測試代碼分離開。由于測試用例脫離測試代碼而存在,可以很方便的進(jìn)行管理和維護(hù)。我們可以為每個(gè)要測試的函數(shù)(或功能)建個(gè)目錄,每個(gè)測試用例放在一個(gè)單獨(dú)的文本文件中,將所有對應(yīng)于該函數(shù)的測試數(shù)據(jù)文件全部放入該目錄下,當(dāng)測試數(shù)據(jù)量很大時(shí)還可以在目錄下再創(chuàng)建相應(yīng)的子目錄,分類進(jìn)行管理。這樣可以方便測試用例的管理,而測試程序也只用專注于測試邏輯。還以測試周期會議預(yù)約功能的例子為基礎(chǔ)進(jìn)行討論。

圖2-1 測試用例目錄結(jié)構(gòu)圖
由圖2-1上可以看到,當(dāng)某個(gè)函數(shù)(或功能)的測試用例很多時(shí),還可以在“某功能測試用例總目錄”下再使用子目錄來劃分,若覺得劃分不夠細(xì)還可以繼續(xù)加深目錄層次,直到分類比較清晰為止。
由于測試用例數(shù)據(jù)和測試代碼進(jìn)行了分離,以后對于某個(gè)函數(shù)(或功能)有了新的測試用例時(shí)不用再去修改測試代碼,只需在該功能的測試數(shù)據(jù)目錄下添加新的測試用例文件即可。該功能的測試程序每次運(yùn)行時(shí)對于相應(yīng)測試數(shù)據(jù)目錄下的每個(gè)測試用例文件(包括各級子目錄下的用例文件)都要執(zhí)行一遍。
使用了目錄結(jié)構(gòu)對它們進(jìn)行了分門別類方便了以后的管理,正如良好的程序應(yīng)具有好的可讀性和維護(hù)性一樣,良好的測試數(shù)據(jù)也應(yīng)具有好的可讀性和維護(hù)性。
2.2測試用例文件結(jié)構(gòu)
測試用例文件中存放測試用例初始化數(shù)據(jù)和測試完畢后的驗(yàn)證數(shù)據(jù)。數(shù)據(jù)的結(jié)構(gòu)采用一般配置文件的格式,詳見圖2-2。

圖2-2 測試用例文件結(jié)構(gòu)圖
1)段名
配置文件中使用了段的概念,段相當(dāng)于C++中的NameSpace,每個(gè)段內(nèi)的關(guān)鍵字(Key)與其它段內(nèi)的關(guān)鍵字互不影響,Section即為段名。
2)關(guān)鍵字
關(guān)鍵字用來標(biāo)志不同元素的值,Key即為關(guān)鍵字的名字,關(guān)鍵字的名字不區(qū)分大小。
3)值
每個(gè)關(guān)鍵字都對應(yīng)一個(gè)值,Value 即為值,值要區(qū)分大小。
4)注釋
支持單行注釋,字符“#”后面的內(nèi)容為注釋。
5)行結(jié)構(gòu)
一行的結(jié)構(gòu)只能是以下幾種:
l [Section]
l Key=Value
l 空行
l 以上三中情況之一加上注釋
2.3測試程序通用庫
測試數(shù)據(jù)從測試代碼中分離出來后,增加了管理測試數(shù)據(jù)文件和解析測試數(shù)據(jù)文件的代碼,這部分代碼是通用的,可以將它們組織成庫供開發(fā)測試程序時(shí)使用。
圖2-3 測試程序和測試通用庫關(guān)系圖
2.3.1管理測試數(shù)據(jù)文件的庫
管理測試數(shù)據(jù)文件的庫的功能如下:
1)獲取目錄下所有測試文件路徑的接口
能找出某個(gè)目錄下 (包括該目錄下所有子目錄)所有的測試文件(如“*.ini”文件)的路徑并存起來。
2)獲取下一個(gè)測試文件的接口
向用戶提供下一個(gè)測試文件的路徑,若已沒有下一測試文件則返回空。
2.3.2解析測試文件的庫
解析測試文件的庫的功能如下:
1)文件解析接口
按照 2.2測試用例文件結(jié)構(gòu) 中的文件結(jié)構(gòu)解析出一個(gè)測試文件。
2)獲取值的接口
向用戶提供測試文件中某個(gè)段內(nèi)某個(gè)關(guān)鍵字對應(yīng)的值。
由于測試文件的結(jié)構(gòu)和一般配置文件結(jié)構(gòu)一致,可以使用已有的庫來實(shí)現(xiàn)(如PWLIB中配置文件解析類)。
3.總結(jié)
一個(gè)完整的測試用例包含測試數(shù)據(jù)和測試代碼,當(dāng)測試數(shù)據(jù)和代碼混在一起時(shí)給測試用例的維護(hù)帶來了很大困難,而且給測試代碼帶來許多冗余。本文提出了將測試數(shù)據(jù)和測試代碼分離的想法,并對怎樣進(jìn)行分離進(jìn)行了闡述。測試數(shù)據(jù)從測試代碼中分離出來后,使得測試數(shù)據(jù)維護(hù)簡單、方便。
4.參考資料
CppUnit 主頁 : http://cppunit.sourceforge.net/
xprogramming 主頁 :http://www.xprogramming.com
4. 關(guān)于作者
溫輝敏,當(dāng)前關(guān)注于軟件開發(fā)自動測試方面,可通過郵件wenhm@sina.com和我聯(lián)系。