一、引言
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)在減少開(kāi)發(fā)努力的同時(shí)也改進(jìn)了軟件的開(kāi)發(fā)質(zhì)量。單元測(cè)試,作為一整套測(cè)試策略的基礎(chǔ),必須是全面的,且要求易于建立和執(zhí)行迅速。然而,對(duì)執(zhí)行環(huán)境和被測(cè)試類(lèi)外部代碼的依賴(lài)性使我們實(shí)現(xiàn)這些目標(biāo)變得更為復(fù)雜。例如,把應(yīng)用程序發(fā)布到容器將顯著地延長(zhǎng)代碼和測(cè)試的周期;而對(duì)其它類(lèi)的依賴(lài)性通常也會(huì)導(dǎo)致測(cè)試的建立更加復(fù)雜和測(cè)試運(yùn)行速度更為緩慢。
集成兩個(gè)流行的測(cè)試框架(StrutsTestCase和EasyMock)來(lái)單元測(cè)試Struts應(yīng)用程序?qū)?huì)更為容易地建立測(cè)試并加快測(cè)試速度。然而,這兩個(gè)框架之間尚存在一些“隔閡”,從而很難把它們理想地集成到一起。在本文中,我將通過(guò)分析兩種方案(一個(gè)面向?qū)ο蟮姆桨负鸵粋(gè)面向方面的方案)來(lái)探討這個(gè)問(wèn)題。同時(shí),我還將展示面向方面編程(AOP)是如何通過(guò)簡(jiǎn)化一些看起來(lái)很困難的問(wèn)題的解決方案而進(jìn)一步補(bǔ)充面向?qū)ο缶幊?OOP)的。
二、集成需要
一個(gè)典型的Struts應(yīng)用程序既能夠展示也其所使用的執(zhí)行環(huán)境也會(huì)體現(xiàn)出類(lèi)之間的依賴(lài)性問(wèn)題;這是因?yàn)镾truts行為(Action)是在一個(gè)servlet容器內(nèi)執(zhí)行的,并且典型情況下會(huì)調(diào)用其它的類(lèi)來(lái)處理請(qǐng)求。模擬對(duì)象測(cè)試方法有助于消除其中不必要的依賴(lài)性。借助于繼承自基本JUnit測(cè)試集的MockStrutsTestCase類(lèi),StrutsTestCase測(cè)試框架提供了對(duì)servlet容器的一種模擬實(shí)現(xiàn)。這顯然方便了容器外測(cè)試,因而也相應(yīng)地加快了單元測(cè)試周期。另一方面,另一個(gè)測(cè)試框架—EasyMock—進(jìn)一步便利了對(duì)協(xié)作類(lèi)的動(dòng)態(tài)模擬(Mock)。這個(gè)框架中所提供的模擬能夠用更簡(jiǎn)單的實(shí)現(xiàn)來(lái)代替真正的類(lèi),并且添加了校驗(yàn)邏輯以支持單元測(cè)試。
非常清楚,把這兩個(gè)框架結(jié)合在一起是非常有益的—Struts應(yīng)用程序便可以在非常真實(shí)的隔離環(huán)境下進(jìn)行測(cè)試。理想情況下,你需要使用下列步驟來(lái)實(shí)現(xiàn)這樣的一個(gè)單元測(cè)試:
1.建立MockStrutsTestCase以便模擬servlet容器。
2.借助于EasyMock來(lái)模擬行為所依賴(lài)的類(lèi)。
3.設(shè)置模擬的期望值。
4.把模擬注入到當(dāng)前測(cè)試的行為中。
5.繼續(xù)進(jìn)行測(cè)試和校驗(yàn)。
注意,上面步驟4中所執(zhí)行的依賴(lài)性注入使被測(cè)試的Struts行為遠(yuǎn)離了其真實(shí)的協(xié)作者而與一個(gè)模擬的行為進(jìn)行交互。為了把通過(guò)EasyMock生成的模擬注入到行為中,你需要從測(cè)試類(lèi)內(nèi)部存取這些行為相應(yīng)的實(shí)例。遺憾的是,這里出現(xiàn)了一種障礙,因?yàn)槲覀儫o(wú)法輕易地從MockStrutsTestCase中實(shí)現(xiàn)這樣的存取。
三、OOP方案
那么,你該如何從MockStrutsTestCase中存取行為實(shí)例呢?首先,讓我們來(lái)分析一下MockStrutsTestCase和Struts的控制器組件之間的關(guān)系。
圖1中展示的關(guān)鍵關(guān)系有可能潛在地導(dǎo)致一種解決上面問(wèn)題的方案。
圖1:此處展示的關(guān)系能夠建立一種OOP方案
◆MockStrutsTestCase中提供了一個(gè)public類(lèi)型的getter方法用于檢索ActionServlet。
◆ActionServlet有一個(gè)protected類(lèi)型的getter方法用于實(shí)現(xiàn)RequestProcessor。
◆RequestProcessor把行為實(shí)例存儲(chǔ)為一個(gè)protected類(lèi)型的成員。
你是否可以子類(lèi)化ActionServlet和RequestProcessor從而使MockStrutsTestCase能夠存取行為呢?相應(yīng)的結(jié)果調(diào)用鏈看上去應(yīng)該如下所示:
myActionTest.getActionServlet().getRequestProcessor().getActions().
注意,在你分析完把MockStrutsTestCase鏈接到Struts行為的調(diào)用序列圖之后,你會(huì)發(fā)現(xiàn)此方法是行不通的。
圖2展示了存在于MockStrutsTestCase和Struts組件之間的關(guān)鍵性交互。
圖2:存在于MockStrutsTestCase和Struts組件之間的交互
圖2展示的問(wèn)題涉及到Struts行為創(chuàng)建的時(shí)序問(wèn)題。到行為內(nèi)部的模擬注入必須在調(diào)用MockStrutsTestCase.actionPerform()之前發(fā)生。然而,此時(shí)這些行為還不可用,因?yàn)橹挥性谡{(diào)用actionPerform()后,RequestProcessor才能夠創(chuàng)建這些行為實(shí)例。
既然你不能很容易地把行為實(shí)例傳播到MockStrutsTestCase中,那么,為什么不子類(lèi)化RequestProcessor并重載processActionCreate()方法呢?在這個(gè)重載方法中,你可以存取所有的行為實(shí)例;這樣以來(lái),創(chuàng)建、配置和設(shè)置對(duì)相應(yīng)行為實(shí)例的一個(gè)模擬一下子變得非常直接。因?yàn)閼?yīng)該在執(zhí)行完actionPerform()之后調(diào)用MockControl.verify()方法,所以,你還需要重載processActionPerform()以進(jìn)行此校驗(yàn)調(diào)用。
這種方案對(duì)于測(cè)試正規(guī)的Struts應(yīng)用程序是不太適合的。因?yàn)榧词顾械男袨閮H與單個(gè)模擬進(jìn)行交互,測(cè)試一個(gè)行為也有可能要求多個(gè)測(cè)試方法—每個(gè)方法都具有不同的模擬期望。為此,我們建議的方案是:創(chuàng)建不同的RequestProcessor子類(lèi),相應(yīng)于每個(gè)子類(lèi)設(shè)置不同的模擬期望。另外,還需要多個(gè)Struts配置文件來(lái)指定不同的RequestProcessor子類(lèi)。終,管理大量的測(cè)試將成為一件令人頭疼的事情。