壹 引言
測試驅動開發在減少開發努力的同時也改進了軟件的開發質量 單元測試 作為壹整套測試策略的基礎 必須是全面的 且要求易於建立和執行迅速 然而 對執行環境和被測試類外部代碼的依賴性使我們實現這些目標變得更為復雜 例如 把應用程序發布到容器將顯著地延長代碼和測試的周期 而對其它類的依賴性通常也會導致測試的建立更加復雜和測試運行速度更為緩慢
集成兩個流行的測試框架(StrutsTestCase和EasyMock)來單元測試Struts應用程序將會更為容易地建立測試並加快測試速度 然而 這兩個框架之間尚存在壹些 隔閡 從而很難把它們理想地集成到壹起 在本文中 我將通過分析兩種方案(壹個面向對象的方案和壹個面向方面的方案)來探討這個問題 同時 我還將展示面向方面編程(AOP)是如何通過簡化壹些看起來很困難的問題的解決方案而進壹步補充面向對象編程(OOP)的
二 集成需要
壹個典型的Struts應用程序既能夠展示也其所使用的執行環境也會體現出類之間的依賴性問題 這是因為Struts行為(Action)是在壹個servlet容器內執行的 並且典型情況下會調用其它的類來處理請求 模擬對象測試方法有助於消除其中不必要的依賴性 借助於繼承自基本JUnit測試集的MockStrutsTestCase類 StrutsTestCase測試框架提供了對servlet容器的壹種模擬實現 這顯然方便了容器外測試 因而也相應地加快了單元測試周期 另壹方面 另壹個測試框架—EasyMock—進壹步便利了對協作類的動態模擬(Mock) 這個框架中所提供的模擬能夠用更簡單的實現來代替真正的類 並且添加了校驗邏輯以支持單元測試
非常清楚 把這兩個框架結合在壹起是非常有益的—Struts應用程序便可以在非常真實的隔離環境下進行測試 理想情況下 妳需要使用下列步驟來實現這樣的壹個單元測試
建立MockStrutsTestCase以便模擬servlet容器
借助於EasyMock來模擬行為所依賴的類
設置模擬的期望值
把模擬註入到當前測試的行為中
繼續進行測試和校驗
註意 上面步驟 中所執行的依賴性註入使被測試的Struts行為遠離了其真實的協作者而與壹個模擬的行為進行交互 為了把通過EasyMock生成的模擬註入到行為中 妳需要從測試類內部存取這些行為相應的實例 遺憾的是 這裏出現了壹種障礙 因為我們無法輕易地從MockStrutsTestCase中實現這樣的存取
三 OOP方案
那麽 妳該如何從MockStrutsTestCase中存取行為實例呢?首先 讓我們來分析壹下MockStrutsTestCase和Struts的控制器組件之間的關系
圖 中展示的關鍵關系有可能潛在地導致壹種解決上面問題的方案
圖 此處展示的關系能夠建立壹種OOP方案
MockStrutsTestCase中提供了壹個public類型的getter方法用於檢索ActionServlet
ActionServlet有壹個protected類型的getter方法用於實現RequestProcessor
RequestProcessor把行為實例存儲為壹個protected類型的成員
妳是否可以子類化ActionServlet和RequestProcessor從而使MockStrutsTestCase能夠存取行為呢?相應的結果調用鏈看上去應該如下所示
?
註意 在妳分析完把MockStrutsTestCase鏈接到Struts行為的調用序列圖之後 妳就會發現此方法是行不通的
圖 展示了存在於MockStrutsTestCase和Struts組件之間的關鍵 *** 互
圖 存在於MockStrutsTestCase和Struts組件之間的交互
圖 展示的問題涉及到Struts行為創建的時序問題 到行為內部的模擬註入必須在調用MockStrutsTestCase actionPerform()之前發生 然而 此時這些行為還不可用 因為只有在調用actionPerform()後 Requ
estProcessor才能夠創建這些行為實例
既然妳不能很容易地把行為實例傳播到MockStrutsTestCase中 那麽 為什麽不子類化RequestProcessor並重載processActionCreate()方法呢?在這個重載方法中 妳可以存取所有的行為實例 這樣以來 創建 配置和設置對相應行為實例的壹個模擬壹下子變得非常直接 因為應該在執行完actionPerform()之後調用MockControl verify()方法 所以 妳還需要重載processActionPerform()以進行此校驗調用
這種方案對於測試正規的Struts應用程序是不太適合的 因為即使所有的行為僅與單個模擬進行交互 測試壹個行為也有可能要求多個測試方法—每個方法都具有不同的模擬期望 為此 我們建議的方案是 創建不同的RequestProcessor子類 相應於每個子類設置不同的模擬期望 另外 還需要多個Struts配置文件來指定不同的RequestProcessor子類 最終 管理大量的測試將成為壹件令人頭疼的事情
四 AOP方案
因此 我們非常希望 在執行某行為之前能夠通過某種方式實現在MockStrutsTestCase中使用該行為的實例 如果妳熟悉AOP 那麽 妳會立即意識到它所提供的簡單方案即能直接滿足這壹要求 註意 這裏的關鍵是定義壹個切點 由它負責捕獲行為執行連接點 然後通過壹個before advice把模擬註入到相應的行為中
在此 我選擇使用AspectJ框架來實現這壹方案 當然 其它的例如Spring AOP這樣的AOP實現也應該能夠良好工作 不過 Spring AOP還需要壹個額外的步驟—通過Spring框架中的DelegatingActionProxy類把對Struts行為的管理委托給Spring
圖 展示了基於AOP方案的單元測試示例靜態模型
圖 基於AOP方案的單元測試示例靜態模型
SimpleAction是壹個Struts行為的子類 同時與ActionService進行協作 其中 SimpleActionTest派生於MockStrutsTestCase 用來測試SimpleAction
SimpleActionTest使用EasyMock創建和建立壹個模擬ActionService SimpleActionTest還實現StrutsActionPreExecuteListener接口以便在即將運行 SimpleAction的execute方法時接收通知 作為通知的壹部分 SimpleActionTest接收SimpleAction實例以便註入ActionService模擬 由方面類StrutsActionPreExecuteNotifier負責通知任何實現監聽器接口的測試類 並且使相應的行為實例可用
下面的步驟描述了實現StrutsActionPreExecuteNotifier的過程
◆首先 由壹個切點選擇相應的測試方法執行連接點 另壹方面 這個測試方法駐留於負責監聽該行為的預執行事件的測試類中 另外 這個切點還會暴露當前執行的測試類對象 pointcut mockStrutsTest(StrutsActionPreExecuteListener actionTest):
?
◆然後 由第二個切點負責捕獲上面的行為執行連接點 通過結合第壹個切點 匹配範圍被限制到該行為相應的測試方法的調用流程的內部 這種進壹步縮小的範圍對行為執行(並非通過測試方法激活)起到過濾作用 最終 方面根本不會影響到最後生成的代碼 該行為及其相應的測試類實例都是經由切點參數加以暴露的 pointcut strutsActionExecute(Action action StrutsActionPreExecuteListener actionTest):
?
◆最後 由壹個與前壹個切點相關聯的before advice負責通知測試類(它們擔任行為事件的監聽器)並且傳遞相應於模擬註入的行為實例
?
圖 展示了這些類之間的動態交互情形
圖 類之間的動態交互
註意 圖中從行為到方面的虛線描述了對行為執行連接點的捕獲情況 此時序圖與第壹個時序圖比較 其重要區別正在於行為執行之前發生的三個步驟
壹個切點捕獲行為執行連接點(由從SimpleAction指向StrutsActionPreExecuteNotifier的虛線箭頭指出)
方面的before advice負責通知測試類並且把相應的行為實例傳遞給它
測試類把模擬對象註入到即將要開始執行的行為實例中
現在 妳可以基於前面概括的五個步驟繼續編寫行為測試 下面的代碼展示了相應於SimpleActionTest的部分代碼 步驟已在註釋中標出
使用MockStrutsTestCase和EasyMock進行行為測試的部分代碼
?
?
在行動及其依賴的服務之間存在四種可能的復合關系
每個行為依賴於壹個服務
每個行為依賴於多個服務 ?
多個行為依賴於壹個服務 ?
多個行為依賴於多個服務 ?
我在此展示的方案能夠比較靈活而且相對容易地支持上面所有這四種情形 因為模擬創建 期望值建立以及模擬註入都能夠在單個的測試類內實現
妳能夠不借助於監聽器接口就可以在StrutsActionPreExecuteNotifier內部模擬註入嗎?這看起來似乎使得測試類實現更簡單壹些 然而 實踐證明 類似早些時候討論的OOP方案 編寫多個方面以創建不同的模擬對象並建立相應的不同的模擬期望是非常必要的 另外 在單個測試類內本地化模擬的創建與安裝(借助於監聽技術 這是可能的)將變得更為方便
五 總結
對於我們在本文中所討論的集成問題 有人可能會創造出壹套相當不錯的OOP方案 然而 構造這種方案很可能需要對Struts和StrutsTestCase有深入的理解才行 並且要付出相當的努力 影響本文中所討論的兩個測試框架(StrutsTestCase和EasyMock)緊密集成的主要障礙在於 在Struts行為實例執行之前很難實現對它的訪問 在認識了導致這種障礙的基本原因之後 AOP方案自然地出現在我們面前 不必再強求於基於傳統型OOP的那種更復雜的方案 AOP允許我們把我們的方案更為緊密地映射到問題空間
lishixinzhi/Article/program/Java/ky/201311/28045