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