基于 Java™ 的 Web 開發領域最近出現了豐富的競爭性技術。啟動新項目的開發人員可以在許多不同的框架之間進行選擇,包括 JavaServer Faces、Tapestry、Shale、Grails 和 Seam (只列舉眾多機靈的名稱中的幾個)。很快,我們就可以通過 JRuby 框架在 Java 編程中使用 Ruby on Rails 了!
但就在不遠的過去,只有一個 Java Web 開發框架卓然而立。Struts 是第一個在 Java 世界掀起風暴的框架,而且多年以來,好像是如果一個項目不用 Struts 構建就沒有前途一樣。沒有 Struts 經驗的 Java 開發人員很稀少,也很不幸,就像今天的開發人員沒有聽說過 Ruby on Rails 一樣。
![]() |
|
即使 Struts 正慢慢地從舞臺中央退去(原來的基本框架,現在叫做 Struts 1,似乎正在退出 Web 框架的歷史舞臺),但它的遺產仍然存在,既以 Shale (請參閱 參考資料)的形式存在,又以運行在世界各地的成千上萬的遺留應用程序的形式存在。因為許多企業寧愿測試和維護這些應用程序而不愿意花錢重新編寫它們,所以理解 Struts 應用程序的一些缺陷,以及如何圍繞它們進行重構,是個好主意。
這個月,我要把以質量為核心的方法用于 Struts 應用程序的測試場景。結合現實,這個場景圍繞著最普遍的 Struts 構造:深受喜愛的 Action
類。
1、2、3,行動!
Struts 的革新之一就是把 Web 開發從 Servlet 移進了 Action
類。這些類包含業務邏輯,以 JavaBean 的形式(通常叫做 ActionForm
)把數據傳送到 JSP。然后 JSP 處理應用程序視圖。Struts 到 MVC 的方法非常容易掌握,以至于許多開發團隊冒失地闖進去,而很少考慮與 Action
相關的長期設計和維護問題。
![]() |
|
雖然在那個時候(過去的自由時光。┛赡軟]人想過,但 Struts Action
類通常成為復雜性的保護所。像在老的 EJB 架構中聲名狼籍的會話 Facade 一樣,Action
類會成為特定業務過程的嚴格偽裝,或者通過直接調用 EJB,通過打開數據庫連接,或者通過調用其他高度依賴的對象。Action
類還有輸出耦合(通過 java.servlet
API 包中的對象,例如 HttpServletRequest
和 HttpServletResponse
),從而極難把它們隔離出來測試。
隔離出來測試 Action
類的困難意味著它們可以很容易變得相當復雜 —— 特別是當它們變成越來越深入地與遺留框架耦合的時候,F在我們來看這個困難在真實的遺留應用程序場景中作用的情況。
測試挑戰
即使最簡單的 Struts Action
類也會是個測試挑戰。例如,以清單 1 中的 execute()
方法為例;它看起來足夠簡單,可以測試,但是真的么?
清單 1. 這個方法看起來容易測試……
|
圖 1. Action 類的輸出耦合

但是,就像在圖 1 中可以看到的,在試圖隔離 ChangePasswordAction
類并檢驗 execute()
方法時,該類給出了一些有代表性的挑戰。為了有效地測試 execute()
方法,必須處理三層耦合。首先,到 Struts 自身的耦合;其次,Servlet API 代表一個障礙;最后,到業務對象包的耦合,進一步檢查業務對象包,還會有數據訪問層使用 Hibernate 和 Spring。
![]() |
|
對于更高的復雜性,請注意 清單 1 中的代碼如何把 aForm
參數轉換成 ChangePasswordForm
對象,它是 Struts ActionForm
類型。這些 JavaBeans 有一個 validate
方法,這個方法由 Struts 在調用 Action
類的 execute()
方法之前調用。
犯錯誤太容易了
在清單 2 中,可以看到所有這個復雜性會在哪里發生。ChangePasswordForm
的 validate()
方法的代碼片段演示了保證兩個屬性(newPassword1
和 newPassword2
)不為空并彼此相等的簡單邏輯。但是,如果 Struts 發現 errors
集合(類型為 ActionErrors
)包含一些 ActionError
對象,就會沿著錯誤路徑走,例如帶著出錯消息重新顯示 Web 頁面。
清單 2. ChangePasswordForm 的驗證邏輯
|
清單 1 和 清單 2 的代碼不特殊也不特定于某個領域。它是無數應用程序中都包含的簡單口令修改邏輯。如果正在測試 Struts 遺留應用程序,將不得不花些時間處理口令邏輯,但是如何用可重復的方式測試它呢?
![]() ![]() |
![]()
|
兩個測試用例
在企圖為 清單 1(間接的是 清單 2)的代碼編寫測試之前,可能想確定實際需要測試什么。在這個具體示例中,邏輯清楚地是為了方便用戶口令的修改;所以,應當編寫至少兩個層次的測試用例:
- 口令修改在數據正確時是否工作?
- 如果數據不正確,口令是不是不 修改?
這些測試不會太容易只是個假設。不僅需要對付 Struts,還必須處理數據層以及數據層與數據庫暗含的耦合!在面對復雜性時,我的第一本能是尋求幫助,在這個示例中,是以 JUnit 的 StrutsTestCase 的形式。
![]() ![]() |
![]()
|
來自 StrutsTestCase 的幫助
StrutsTestCase 是一個 JUnit 擴展,專門針對 Struts 應用程序。這個框架實際上模擬了一個 servlet 容器,這樣就能虛擬地運行和測試 Struts 應用程序,而不必在 Tomcat(舉例)中運行它了?蚣苓有一個方便的 MockStrutsTestCase
類,它擴展了 TestCase
并處理許多 Struts 配置方面(例如裝入 struts-config.xml 配置文件)。
但是,在您認為自己完全脫離了 Struts 配置的痛苦之前,應當了解一些正確配置 MockStrutsTestCase
的事情。也就是說,需要把它指向代表 Web 應用程序的目錄,然后指向必要的 web.xml 和 struts-config.xml 文件。默認情況下,MockStrutsTestCase
掃描這些項目的類路徑;但是,要把 MockStrutsTestCase
配置成在特定環境中工作,操作很簡單,只需覆蓋一些設置并編寫一些特定的配置代碼即可。
返回口令驗證示例,包含 ChangePasswordAction
類的項目有圖 3 所示的目錄結構:
清單 3. 示例目錄結構
|
WEB-INF 目錄包含 web.xml 和 struts-config.xml 文件,webapp 目錄代表 Web 上下文環境。知道了這些,我就如清單 4 所示配置 MockStrutsTestCase
:
清單 4. MockStrutsTestCase 的定制 fixture 代碼
|
![]() |
|
關于邏輯映射
正確地配置了 MockStrutsTestCase
的實例后,測試 Action
類就只包含一點點邏輯映射。要調用 Action
類,需要強制 StrutsTestCase 框架通過一個路徑間接地 調用它,這是在 struts-config.xml 文件中定義的。
例如,要強制調用 ChangePasswordAction
類,必須告訴框架使用 /changePasswordSubmit
路徑。在清單 5 中可以看到這點,清單 5 中的代碼片段來自 struts-config.xml 文件,它把 ChangePasswordAction
類映射到 /changePasswordSubmit
路徑:
清單 5. struts-config.xml 代碼片段顯示了動作類路徑映射
|
一旦某個用戶點擊了提交按鈕(舉例),Struts 就把來自 HTTP 請求的參數值映射到 ActionForm
,在這個示例中,是上面的 struts-config.xml 代碼片段中(在清單 5 中)定義的 ChangePasswordForm
。要模擬這個行為,在測試用例中必須有另一個邏輯映射 —— JSP 表單名稱必須映射到值。在口令修改場景中,提交了四個參數:username
、currentPassword
、newPassword1
和 newPassword2
( newPassword2
參數是多數 Web 頁面為了校驗新口令正確的確認信息)。
![]() ![]() |
![]()
|
成功的測試用例!
請求路徑和參數映射好之后,編寫測試用例就成了利用 MockStrutsTestCase
API 設置相關口令值的問題,如清單 6 所示。在這個測試用例中,用戶 Jane 的口令從 “admin” 改成了 “meme”。
清單 6. 一個驗證口令修改成功的簡單測試用例
|
setRequestPathInfo()
方法配置路徑以映射到 Action
類,addRequestParameter()
方法把來自 JSP 文件的參數名稱映射到值。例如,在清單 6 中,username
參數映射到 “jane”。
還請注意清單 6 中的最后兩行。actionPerform()
方法實際上讓 Struts 去調用對應的 Action
類。如果這個方法沒被調用,什么也不會發生。最后調用的方法 verifyForward()
是在 MockStrutsTestCase
類中找到的一個類似于斷言的方法,它驗證正確的轉發。在 Struts 中,這是一個 String
,通常映射到成功或失敗狀態。(請注意,清單 5 中的 XML 定義了 “success” 轉發。)
![]() ![]() |
![]()
|
用 DbUnit 進行的可重復的成功
這時,您可能希望工作完成 —— 畢竟已經編寫了一個企圖驗證口令修改的測試。但是還缺乏更深的驗證。確實,這個方便的框架調用了 Struts,但是代碼依賴于數據庫。如果希望能夠不止一次地運行這個測試,比如在構建過程中,就需要讓它可重復。
由于一些特定的假設,所以 清單 6 中的測試用例不是可重復的。首先,測試用例假設在系統中已經 有一個名為 “jane” 的用戶,它的口令是 “admin”。其次,測試用例假設在某些永久存儲 中口令 “admin” 被更新成 “meme”。正如所寫的那樣,只要代碼沒有生成異常,ActionForm
成功驗證,Struts 就假定事情工作良好,測試用例也是一樣。
現在需要的是更深層次的驗證 —— 在數據庫層次。對于應當更新口令的測試用例來說,理想情況下應當在數據庫上 執行檢查,確保那里有一個新口令。對于口令不應當修改的測試來說,需要進行驗證,真正檢驗沒有修改 口令。最后,要讓這個測試套件可重復,最好是不要 對數據完整性做任何假設。
DbUnit 是個專門方便把數據庫放進測試狀態中已知狀態的 JUnit 擴展。使用 XML 種子文件,可以把特定數據插入到測試用例可以依靠的數據庫中。而且,使用 DbUnit API,可以容易地比較數據庫的內容和 XML 文件的內容,從而提供一個在應用程序代碼之外 校驗預期數據庫結果的機制。
![]() ![]() |
![]()
|
用 DbUnit 進行測試
要使用 DbUnit,需要兩樣東西:
- 通過普通 JDBC 的數據庫連接
- 一個文件,包含需要放到數據庫中的數據
清單 7 是一個 DbUnit 種子文件,只定義了幾樣東西:首先,有一個叫做 user
的表和另一個叫做 user_role
的表。在 user
表中定義了一個新行,并映射一些值到列(例如列 username
擁有值 “jane”)。在 user_role
中還定義了一行。請注意這個數據庫中的口令是通過 SHA 加密的。
清單 7. 用于測試表 user 和 user_role 的 DbUnit 種子文件
|
有了這個文件,就可以利用 DbUnit 插入數據、更新數據庫來反映數據,甚至刪除數據。數據庫修改邏輯包含在 DbUnit 的 DatabaseOperation
類中。在這個示例中,只是通過 清單 4 中定義的 MockStrutsTestCase
類型的 setUp()
方法中的一些增強的 fixture 邏輯中的 CLEAN_INSERT
標志來保證干凈的數據集。例如,在清單 8 中,定義了三個方法,分別利用 DbUnit API 把 dbunit-user-seed.xml 文件的內容插入數據庫。
清單 8. 定制的 DbUnit fixture 邏輯
|
清單 8 中定義的 executeSetUpOperation()
方法將在前面的 清單 4 中定義的 setUp()
方法中調用。這個方法再調用清單 8 中的另兩個方法:getDataSet()
把 XML 轉換成 DbUnit 的 IDataSet
類型,getConnection()
則返回包裝成 DbUnit 的 IDatabaseConnection
類型的數據庫連接。
![]() ![]() |
![]()
|
更好的測試用例
配置好 DbUnit 后,剩下的就只有改進 清單 6 的測試用例,驗證數據庫中的一切 OK。然后,添加驗證其他問題場景的其余測試用例。
要確認數據庫中的口令更新,可以使用 DbUnit 的查詢 API,它幫助比較數據庫的結果與靜態定義的 XML 文件,例如清單 9 中定義的那個。請注意這個文件沒有列出 user
表中的所有列 —— 實際上,它只列出了兩個:username
和 password
。
清單 9. 比較測試 XML 文件
|
DbUnit 的查詢 API 足夠靈活,可以幫助過濾掉沒有意義的值,在這個示例中就是 username
和 password
之外的值。同樣,在清單 10 中,verifyPassword()
方法用 DbUnit 的 createQueryTable()
方法構建 ITable
類型,以與清單 9 中定義的 XML 進行比較:
清單 10. 使用 DbUnit 查詢 API 的 verifyPassword 方法
|
Assertion
類型是 DbUnit 定義的定制類,可以進行特定于數據庫結果集的額外斷言。還請注意 verifyPassword()
接受一個文件路徑,這意味著我可以定義多個期望的數據集(一個用于修改的口令,一個用于相同的口令)。
![]() ![]() |
![]()
|
反復測試 Struts
綜合起來,現在有了一個可以完成以下工作的測試用例:
- 通過 DbUnit 填充數據庫
- 配置 Struts
- 間接地調用
ChangePasswordAction
和ChangePasswordForm
類 - 關聯參數值
- 驗證成功轉發
- 驗證數據庫內容
從清單 11 可以看出,ChangePasswordAction
測試用例只通過 testExecute
測試處理一個正常場景:
清單 11. ChangePasswordAction 測試用例
|
只多一個測試……
請注意這個測試用例沒有測試邊界用例,例如:如果兩個口令字段(newPassword1
和 newPassword2()
)不匹配。謝天謝地,一旦設置好了,添加另一個測試用例并不難。在清單 12 中,驗證了如果兩個值不匹配,就生成 ActionError
,用戶 “jane” 口令在數據庫中的值保持不變。
清單 12. 添加新測試
|
在清單 12 中,我驗證了 清單 2 中的邏輯正確地捕捉到了口令值不匹配的情況。MockStrutsTestCase
類包含一個方便方法可以斷言錯誤條件,這個方法是 verifyActionErrors()
,在這個方法中,錯誤 String
被傳遞進來進行驗證。還請注意,數據庫被檢查,這次是根據另一個包含相同值的文件(在這個示例中,username
“jane” 有一個未加密的 password
“admin”)。
![]() ![]() |
![]()
|
Struts 的集成測試
多數 Struts 應用程序不會 很快消失,所以重要的是知道如何在重寫之前用開發人員測試構建一定層次的保證。這個月,我介紹了在測試 Struts 遺留應用程序時的一些挑戰,并介紹了如何用 StrutsTestCase 和 DbUnit 處理它們。
StrutsTestCase 只要配置正確就會處理 Struts 的工作,而 DbUnit 處理與數據庫有關的代碼的工作。一起使用這兩個框架,可以在 Struts 應用程序上進行集成級別的測試,而不用通過更高層次的框架(例如 JWebUnit 或 Selenium)模擬瀏覽器(也是一個值得采用的方法,但是生成的結果非常不同。)
Struts 應用程序對測試來說是 具有挑戰性的,并且沒有解決的方法。這個困難是 Struts 框架被更加創新的框架所掩蓋的原因之一,特別是那些解決了測試容易問題的框架。另一方面,就像我在這里介紹的,測試 Struts 是 可能的 —— 只是需要費些力氣。
文章來源于領測軟件測試網 http://www.kjueaiud.com/
版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備10010545號-5
技術支持和業務聯系:info@testage.com.cn 電話:010-51297073
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月