摘 要: 為了解決單元測(cè)試工具Nunit本身不支持?jǐn)?shù)據(jù)驅(qū)動(dòng)測(cè)試的問(wèn)題,,提出了在Nunit框架下實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測(cè)試的方法。該方法首先將測(cè)試類所使用的測(cè)試數(shù)據(jù)基本信息設(shè)定在ini文件中,,將輸入數(shù)據(jù)及預(yù)期結(jié)果存放于Excel文件中,。隨后通過(guò)屬性標(biāo)簽[TestFixtureSetUp]標(biāo)記的方法動(dòng)態(tài)讀取ini文件中的基本信息,再根據(jù)這些基本信息讀取Excel文件中的測(cè)試數(shù)據(jù),,并將測(cè)試數(shù)據(jù)保存于自定義的結(jié)構(gòu)體數(shù)組中供各測(cè)試方法使用,。該方法有效地實(shí)現(xiàn)了測(cè)試數(shù)據(jù)與測(cè)試腳本的分離,能降低測(cè)試腳本的維護(hù)工作量,,提高測(cè)試效率,。
關(guān)鍵詞: 單元測(cè)試; Nunit框架; 測(cè)試腳本; 測(cè)試數(shù)據(jù)
軟件測(cè)試是保證軟件質(zhì)量的重要手段,作為軟件測(cè)試基礎(chǔ)的單元測(cè)試是軟件開(kāi)發(fā)過(guò)程中最低級(jí)的測(cè)試活動(dòng)。據(jù)業(yè)界統(tǒng)計(jì),,單元測(cè)試一般可以發(fā)現(xiàn)大約80%的軟件缺陷[1],。由于軟件缺陷發(fā)現(xiàn)得越早,其修復(fù)的代價(jià)就越小,,因此,,單元測(cè)試在軟件測(cè)試過(guò)程中占據(jù)著非常重要的地位,有效地實(shí)施單元測(cè)試能有效節(jié)約后續(xù)測(cè)試時(shí)間,、降低測(cè)試成本,、保證軟件質(zhì)量。
Nunit是微軟.Net平臺(tái)下的單元測(cè)試框架,,也是在.Net平臺(tái)下進(jìn)行測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的重要工具,;其提供的圖形用戶接口GUI操作簡(jiǎn)單、內(nèi)容直觀,,因此Nunit被廣泛地運(yùn)用于.Net平臺(tái)項(xiàng)目的單元測(cè)試中,。利用Nunit進(jìn)行單元測(cè)試,首先要進(jìn)行腳本開(kāi)發(fā),,目前常用的方法是將測(cè)試數(shù)據(jù)以常量的方式賦值給變量,,再由變量參與測(cè)試[2-3]。采用這種方式,,測(cè)試數(shù)據(jù)與測(cè)試腳本共存于一個(gè)測(cè)試腳本文件中,,不利于測(cè)試腳本的維護(hù)。而測(cè)試腳本的維護(hù)是自動(dòng)化測(cè)試的重要環(huán)節(jié),,適當(dāng)?shù)卣{(diào)整和增強(qiáng)測(cè)試腳本,,能提高測(cè)試腳本的靈活性以及應(yīng)對(duì)測(cè)試對(duì)象變更的能力,數(shù)據(jù)驅(qū)動(dòng)方式的測(cè)試腳本開(kāi)發(fā)是解決這類問(wèn)題的重要手段[4],。由于Nunit框架本身并不具備數(shù)據(jù)驅(qū)動(dòng)測(cè)試的功能,,因此需要通過(guò)增強(qiáng)測(cè)試腳本的功能來(lái)實(shí)現(xiàn)Nunit框架下的數(shù)據(jù)驅(qū)動(dòng)測(cè)試。為此,,本文提出一種測(cè)試設(shè)計(jì)方法,,將測(cè)試用例的輸入數(shù)據(jù)、預(yù)期結(jié)果及執(zhí)行條件編寫(xiě)在Excel工作簿中,,工作簿中每一個(gè)工作表的數(shù)據(jù)對(duì)應(yīng)一個(gè)測(cè)試類;工作簿存放的位置,、測(cè)試類對(duì)應(yīng)的工作表名,、輸入數(shù)據(jù)數(shù)目及預(yù)期結(jié)果數(shù)目等信息按一定格式保存于執(zhí)行路徑下的ini文件中。在測(cè)試腳本中,,利用Nunit的[TestFixtureSetUp]屬性標(biāo)簽,,在該標(biāo)簽標(biāo)記的方法中實(shí)現(xiàn)測(cè)試類所有測(cè)試數(shù)據(jù)的一次性讀入,并將其保存于自定義的結(jié)構(gòu)體數(shù)組中,供各測(cè)試方法使用,,在后續(xù)測(cè)試方法的腳本編寫(xiě)中,,只需按結(jié)構(gòu)體數(shù)組下標(biāo)獲取測(cè)試數(shù)據(jù)及預(yù)期結(jié)果便能完成測(cè)試腳本的開(kāi)發(fā);測(cè)試執(zhí)行時(shí)將按照Excel文件中設(shè)計(jì)的數(shù)據(jù)自動(dòng)執(zhí)行測(cè)試腳本,。這一測(cè)試設(shè)計(jì)方法能有效地分離測(cè)試腳本和測(cè)試數(shù)據(jù),,從而降低測(cè)試腳本的維護(hù)工作量。
1 數(shù)據(jù)驅(qū)動(dòng)測(cè)試的設(shè)計(jì)思路
數(shù)據(jù)驅(qū)動(dòng)測(cè)試是一種數(shù)據(jù)被包含在輸入測(cè)試數(shù)據(jù)文件中,,并且以數(shù)據(jù)來(lái)控制自動(dòng)化測(cè)試腳本執(zhí)行的流程和動(dòng)作的測(cè)試[5],。在Nunit框架中,[TestFixtureSetUp]和[SetUp]屬性標(biāo)簽標(biāo)記的方法分別在測(cè)試類和各測(cè)試方法執(zhí)行前被執(zhí)行,[Test]屬性標(biāo)簽標(biāo)記的方法按其在腳本中的先后順序自動(dòng)執(zhí)行;此外,,通過(guò)Nunit的GUI界面,,可以指定被執(zhí)行的方法,因此Nunit自身已經(jīng)具備控制測(cè)試腳本執(zhí)行流程的功能,。這里主要從用數(shù)據(jù)驅(qū)動(dòng)腳本的動(dòng)作出發(fā)來(lái)實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測(cè)試,。
數(shù)據(jù)驅(qū)動(dòng)測(cè)試使用存檔的測(cè)試數(shù)據(jù)來(lái)驅(qū)動(dòng)自動(dòng)化測(cè)試過(guò)程,這些數(shù)據(jù)通常以簡(jiǎn)單的文本文件或Excel文件形式存在[5],。鑒于Excel文件具有以表格形式呈現(xiàn),、結(jié)構(gòu)清晰直觀的特點(diǎn),本文采用Excel的工作表來(lái)存儲(chǔ)測(cè)試數(shù)據(jù)。同時(shí),,為使測(cè)試腳本具有較高的靈活性,,將測(cè)試數(shù)據(jù)文件的存取路徑、測(cè)試數(shù)據(jù)所在的工作表名等信息存放于ini文件中,以便在測(cè)試環(huán)境和數(shù)據(jù)發(fā)生改變時(shí),,測(cè)試腳本可以保持不變,從而降低測(cè)試腳本的維護(hù)成本,。
1.1 Excel文件格式的設(shè)計(jì)
圖1為Excel文件結(jié)構(gòu)與測(cè)試類及測(cè)試方法之間的對(duì)應(yīng)關(guān)系圖,圖中的數(shù)據(jù)是根據(jù)Nunit自帶的一個(gè)實(shí)例Moneybag類所做的部分測(cè)試數(shù)據(jù),。
圖1中,,工作簿Moneytest.xls中的工作表Moneytest用來(lái)存放測(cè)試類Moneytest中各測(cè)試方法所需的數(shù)據(jù)。由于各測(cè)試方法常常使用一些公共的輸入數(shù)據(jù)來(lái)進(jìn)行測(cè)試驗(yàn)證,,因此將公共數(shù)據(jù)設(shè)定在第一行,,從第二行數(shù)據(jù)開(kāi)始每一行數(shù)據(jù)對(duì)應(yīng)一個(gè)測(cè)試方法,每一個(gè)單元格存放一個(gè)基本數(shù)據(jù)(字符串類型或數(shù)值),。結(jié)構(gòu)數(shù)據(jù)類型都可以看成是基本數(shù)據(jù)類型的組合,,一個(gè)結(jié)構(gòu)數(shù)據(jù)類型可分成多個(gè)單元格來(lái)存放。由于每一個(gè)測(cè)試方法所需的輸入和預(yù)期值數(shù)目各不相同,,在測(cè)試數(shù)據(jù)設(shè)計(jì)時(shí),,可按需要增減工作表列的數(shù)目來(lái)增減輸入和預(yù)期值的數(shù)目(圖1中3~6行的A-H列為空,是因?yàn)?~6行對(duì)應(yīng)的測(cè)試方法全部采用公共輸入數(shù)據(jù)),。測(cè)試數(shù)據(jù)的最后一列為測(cè)試說(shuō)明,,用于對(duì)測(cè)試條件等輔助信息加以說(shuō)明,,以供測(cè)試人員或維護(hù)人員參考,測(cè)試腳本不讀取該列數(shù)據(jù),。
1.2 ini文件的設(shè)計(jì)
使用ini文件是為了使這一實(shí)現(xiàn)方法具有較高的靈活性,,最大限度地減少因測(cè)試環(huán)境和測(cè)試數(shù)據(jù)的變化帶來(lái)的測(cè)試腳本的維護(hù)工作量。這里約定ini文件以標(biāo)記的測(cè)試類命名,,并且保存于執(zhí)行文件所在目錄下,,這樣可通過(guò)編程來(lái)動(dòng)態(tài)讀取路徑。因此,,除了ini文件名外,,其他所有數(shù)據(jù)的來(lái)源都是通過(guò)測(cè)試腳本執(zhí)行動(dòng)態(tài)獲取的,這一實(shí)現(xiàn)方法能達(dá)到較高程度的測(cè)試腳本與測(cè)試數(shù)據(jù)的分離,。
ini文件設(shè)計(jì)如下:
[FILE]
path= "D:\NUNIT\Moneytest.xls"
sheet= [Moneytest$]
[DATA]
input=8
except=4
[ROWS]
Common=0
BagSimpleAdd=1
BagSubtract=2
BagMultiply=3
BagNegate=4
節(jié)[FILE]用于存放Excel數(shù)據(jù)文件信息,,參數(shù)path的值表示數(shù)據(jù)文件存放的路徑,參數(shù)sheet的值表示測(cè)試數(shù)據(jù)所在的工作表名,;節(jié)[DATA]用于存放輸入數(shù)據(jù)和預(yù)期值的數(shù)目,,這里按該測(cè)試類中使用輸入數(shù)據(jù)和預(yù)期值數(shù)目最多的方法進(jìn)行設(shè)置,用于動(dòng)態(tài)定義結(jié)構(gòu)體內(nèi)數(shù)組的維數(shù),,這樣定義會(huì)損失一定的數(shù)組空間,,但給測(cè)試腳本的編寫(xiě)帶來(lái)了方便;節(jié)[ROWS]用于存放每個(gè)測(cè)試方法對(duì)應(yīng)的測(cè)試數(shù)據(jù)所在的行編號(hào)(結(jié)構(gòu)體數(shù)組下標(biāo)),,在各測(cè)試方法中通過(guò)讀取ini文件中的行編號(hào)來(lái)取得對(duì)應(yīng)的測(cè)試數(shù)據(jù),,Common=0表示將公用數(shù)據(jù)設(shè)在數(shù)據(jù)表第一行,讀入到下標(biāo)為0的數(shù)組中,。
2 數(shù)據(jù)驅(qū)動(dòng)測(cè)試的實(shí)現(xiàn)
Nunit采用屬性標(biāo)簽來(lái)標(biāo)記測(cè)試類和方法,其中[TestFixture]用于標(biāo)記測(cè)試類,,[Test]、[SetUp],、[TestFixtureSetUp]用于標(biāo)記測(cè)試方法,。[SetUp]標(biāo)記的方法是為了避免代碼的冗余,該方法將各測(cè)試方法中的重復(fù)代碼提取出來(lái),,組織成一個(gè)共用的方法,,在每個(gè)[Test]標(biāo)記的測(cè)試方法執(zhí)行前被執(zhí)行一次,與它成對(duì)使用的是[TearDown]屬性,,用來(lái)釋放[SetUp]中初始化的變量,。[TestFixtureSetUp]與[SetUp]屬性類似,但此屬性標(biāo)記的方法用來(lái)實(shí)現(xiàn)整個(gè)測(cè)試類的初始化,,它在整個(gè)測(cè)試類執(zhí)行前執(zhí)行一次,,與它成對(duì)使用的是[TestFixtureTearDown]屬性,用來(lái)釋放[TestFixtureSetUp]中初始化的變量,。
在本設(shè)計(jì)中,測(cè)試數(shù)據(jù)被存儲(chǔ)在外部數(shù)據(jù)文件中,每一個(gè)方法執(zhí)行前都需要外部文件中的測(cè)試數(shù)據(jù),,如果將讀取外部文件的操作分別寫(xiě)到各個(gè)測(cè)試方法中,,這無(wú)疑會(huì)產(chǎn)生大量冗余代碼,也會(huì)增加I/O方面的開(kāi)銷,;如果將這部分處理寫(xiě)到[SetUp]屬性方法中,,可減少測(cè)試腳本中重復(fù)代碼的數(shù)量,但I(xiàn)/O方面的開(kāi)銷與前一種方法相比,,沒(méi)有任何改善,。故本設(shè)計(jì)將測(cè)試數(shù)據(jù)讀取的代碼編寫(xiě)在[TestFixtureSetUp]屬性標(biāo)記的方法中,通過(guò)自定義一個(gè)結(jié)構(gòu)體,,將測(cè)試數(shù)據(jù)一次性讀出并存放于結(jié)構(gòu)體數(shù)組中,,即在整個(gè)測(cè)試類的方法執(zhí)行之前執(zhí)行一次測(cè)試數(shù)據(jù)的讀取操作。這樣,,既能減少測(cè)試腳本中重復(fù)代碼的數(shù)量,也能減少I/O方面的開(kāi)銷,。
2.1 自定義結(jié)構(gòu)體的實(shí)現(xiàn)
public struct testdata
{
public string[] strIn ;
public string[] strExcept;
}
上面是采用C#定義的結(jié)構(gòu)體testdata,其中包含兩個(gè)字符串類型的動(dòng)態(tài)數(shù)組,,分別用來(lái)存放各測(cè)試方法的輸入數(shù)據(jù)和預(yù)期結(jié)果,。因?yàn)闇y(cè)試類最大的輸入數(shù)據(jù)和預(yù)期結(jié)果數(shù)目在測(cè)試用例維護(hù)時(shí)可能發(fā)生變化,故采用定義動(dòng)態(tài)數(shù)組的方式,。
2.2 測(cè)試類初始化的實(shí)現(xiàn)
[TestFixtureSetUp]屬性標(biāo)記的方法用于整個(gè)測(cè)試類的初始化,,其處理流程設(shè)計(jì)如圖2所示。
過(guò)程①中讀取[FILE]和[DATA]節(jié)中的值,,用于定位測(cè)試數(shù)據(jù)文件和定義結(jié)構(gòu)體數(shù)組維數(shù),;過(guò)程②按行列依次循環(huán),將數(shù)據(jù)表中的數(shù)據(jù)依次全部讀入結(jié)構(gòu)體數(shù)組,;過(guò)程③初始化各測(cè)試方法的公用數(shù)據(jù),,下面是按照本文提出的方法,以Nunit自帶的樣例測(cè)試腳本MoneyTest.cs為例對(duì)公共輸入數(shù)據(jù)進(jìn)行設(shè)定的相關(guān)代碼:
f12CHF = new Money(Convert.ToInt32(tdTest[i].strIn[0]), tdTest[i].strIn[1]);
f7USD = new Money(Convert.ToInt32(tdTest[i].strIn[2]), tdTest[i].strIn[3]);
f14CHF = new Money(Convert.ToInt32(tdTest[i].strIn[4]), tdTest[i].strIn[5]);
f21USD = new Money(Convert.ToInt32(tdTest[i].strIn[6]), tdTest[i].strIn[7]);
fMB1 = new MoneyBag(f12CHF, f7USD);
fMB2 = new MoneyBag(f14CHF, f21USD);
如果約定下標(biāo)為0的結(jié)構(gòu)體數(shù)組中存放公共測(cè)試數(shù)據(jù),,則在上面的代碼前可直接將i賦值為0,,也可讀取ini文件的Common值來(lái)設(shè)定i值。
2.3 自定義方法的實(shí)現(xiàn)
每一個(gè)方法執(zhí)行之前都需要讀取結(jié)構(gòu)體數(shù)組中對(duì)應(yīng)行的數(shù)據(jù),,雖然讀取數(shù)據(jù)的代碼可以寫(xiě)在各測(cè)試方法中,,但這無(wú)疑會(huì)產(chǎn)生重復(fù)代碼。通過(guò)分析各測(cè)試方法讀取數(shù)據(jù)的相似部分,,可將這部分代碼寫(xiě)成一個(gè)公用方法,,[SetUp]中定義的方法能保證各方法執(zhí)行之前自動(dòng)執(zhí)行一次,但是該方法不能帶參數(shù),。因此,,這里自定義一個(gè)帶參數(shù)的方法供各測(cè)試方法調(diào)用,。同樣以Moneybag類的測(cè)試為例,該測(cè)試類中,,各方法的輸入基本采用公共數(shù)據(jù),,各方法的預(yù)期結(jié)果各不相同,因此各方法均有一個(gè)讀取預(yù)期結(jié)果的公共處理,,將這部分處理用自定義方法實(shí)現(xiàn),,參數(shù)i為各方法對(duì)應(yīng)的測(cè)試數(shù)據(jù)的數(shù)組下標(biāo)。代碼如下:
private void SetValue(int i)
{
mexcept1 = new Money(Convert.ToInt32(tdTest[i].strExcept[0]), tdTest[i].strExcept[1]);
mexcept2= new Money(Convert.ToInt32(tdTest[i].strExcept[2]), tdTest[i].strExcept[3]);
mbexcept = new MoneyBag(mexcept1, mexcept2);
}
數(shù)據(jù)表中的數(shù)據(jù)全部以字符串類型讀入構(gòu)造體數(shù)組中,,使用時(shí),,根據(jù)具體情況將字符串類型轉(zhuǎn)換為對(duì)應(yīng)數(shù)據(jù)類型參與測(cè)試。公共處理以外的個(gè)性數(shù)據(jù)讀取可在各測(cè)試方法中實(shí)現(xiàn),。
2.4 各測(cè)試方法的實(shí)現(xiàn)
將Nunit自帶的樣例測(cè)試腳本按照本文提出的方法進(jìn)行改編,,下面是實(shí)現(xiàn)單一貨幣錢(qián)包加法的改編腳本(其他方法基本類似):
[Test]
public void BagSimpleAdd()
{
int intI = GetPrivateProfileString("ROWS", "BagSimpleAdd", "", tmpRow, 500, striniPath + "\\moneytest.ini");
int i = Convert.ToInt32(Convert.ToString(tmpRow));
SetValue(i);
Assert.AreEqual(mbexcept, fMB1.Add(f14CHF));
}
利用本文提出的方法對(duì)Nunit自帶測(cè)試類Monytest.cs中的所有方法進(jìn)行改編,執(zhí)行結(jié)果與自帶測(cè)試類一致,。與原來(lái)的方法相比較,,本文提出的方法實(shí)現(xiàn)了測(cè)試腳本與測(cè)試數(shù)據(jù)的分離,對(duì)測(cè)試用例的維護(hù)基本可通過(guò)對(duì)Excel數(shù)據(jù)文件和ini文件的維護(hù)來(lái)實(shí)現(xiàn),,從而降低了測(cè)試腳本維護(hù)過(guò)程中產(chǎn)生的維護(hù)成本,。在后續(xù)的研究中,將根據(jù)本文提出的方法,,進(jìn)一步探索測(cè)試腳本自動(dòng)生成的實(shí)現(xiàn),,以進(jìn)一步提高測(cè)試腳本的開(kāi)發(fā)效率。
參考文獻(xiàn)
[1] 劉德寶.軟件測(cè)試工程師培訓(xùn)教材[M].北京:科學(xué)出版社,,2009.
[2] 林勤花.使用NUnit在.net編程中進(jìn)行單元測(cè)試[J].科技信息,,2008(24):410-411.
[3] 陸復(fù)名. NUnit.NET項(xiàng)目測(cè)試點(diǎn)評(píng)[J].程序員,2004(11):128-129.
[4] 陳技能.QTP自動(dòng)化測(cè)試實(shí)踐[M]. 北京:電子工業(yè)出版社,,2009.
[5] 劉曉丹,,武君勝,劉博.基于數(shù)據(jù)驅(qū)動(dòng)的自動(dòng)化測(cè)試平臺(tái)設(shè)計(jì)[J].科學(xué)技術(shù)與工程,,2008,8(3):779-782.