StrutsTestCase 是一種用于測試Struts Actions的強大而且易于使用的測試框架。Struts和StrutsTestCase,結合傳統的JUnit 測試,將會(huì )帶來(lái)你高覆蓋率的測試,從而提高產(chǎn)品的可靠性。
StrutsTestCase 是一種基于JUnit, 用于測試 Struts ,用于測試應用程序的Struts action 類(lèi)的一種簡(jiǎn)單、高效的方式。
典型的J2ee應用程序的層次結構如圖1所示:
· DAO層用于封裝對數據庫訪(fǎng)問(wèn)。
· 在DAO層,你會(huì )發(fā)現Hibernate 映射、類(lèi)、查詢(xún)、實(shí)體EJB或者其他的實(shí)體-關(guān)系持久技術(shù)。
· 業(yè)務(wù)邏輯層包含更多的高級的業(yè)務(wù)服務(wù)。在理想狀態(tài)下,業(yè)務(wù)層獨立于數據庫實(shí)現。事務(wù)EJB在這層經(jīng)常被使用。
· 表達層負責為用戶(hù)顯示應用程序數據和解釋用戶(hù)的請求。在Struts應用程序中,表達層經(jīng)常使用JSP/JSTL顯示數據,用Struts action處理用戶(hù)的請求。
· 基本上,客戶(hù)層是運行在客戶(hù)機器上的web瀏覽器??蛻?hù)端邏輯(比如:JS)有時(shí)候被放到這一層,雖然它是難以有效被測試的。
Figure 1. Typical J2EE architecture
DAO和業(yè)務(wù)層可以用經(jīng)典的JUnit,或者其他一些JUnit擴展工具做測試。DbUnit就是一個(gè)的做數據庫單元測試的很好選擇。(更多DbUnit知識,查看Andrew Glover的“用DbUnit做高效的單元測試”)
另一方面,對struts的Action進(jìn)行測試是很困難的。即使當業(yè)務(wù)邏輯很好的被限定在業(yè)務(wù)層,Struts action通常還是會(huì )包含很重要的數據驗證、數據轉換和數據流控制代碼。不對Struts action 支路進(jìn)行測試在代碼的覆蓋率上會(huì )有很多的不足。StrutsTestCase 會(huì )彌補這些不足。
對action層進(jìn)行單元測試也會(huì )帶來(lái)其他的好處:
· 視圖層和業(yè)務(wù)層會(huì )更容易理解,更加簡(jiǎn)單和更清楚。
· 重構Action類(lèi)會(huì )變得更容易。
· 避免多余、無(wú)用的action類(lèi)。
· 測試能有助于A(yíng)citon層的文檔的編寫(xiě)(在寫(xiě)jsp頁(yè)面時(shí)候有作用)
這是測試驅動(dòng)開(kāi)發(fā)的常見(jiàn)的一些好處,并且這些好處是除了在Sturts Action層測試還是其他的一些地方都是通用的。
StrutsTestCase介紹StrutsTestCase工程提供了一種在JUnit框架下測試struts action的靈活、便利的方法。你可以通過(guò)設置請求參數,檢查在A(yíng)ction被調用后的輸出請求或Session狀態(tài)這種方式對Struts Action做白盒測試。
StrutsTestCase 提供了用框架模擬web容器的模擬測試方法也提供了真實(shí)的web容器(比如:Tomcat)下的測試方法。一般來(lái)說(shuō),我更喜歡模擬測試,因為它更加的輕便、運行更快,因而更經(jīng)湊的開(kāi)發(fā)。
所有的StrutsTestCase單元測試類(lèi)都源于模擬測試的 MockStrutsTestCase或容器內測試的CactusStrutsTestCase。
在這里我們更關(guān)注mock測試,因為它需要更少的啟動(dòng)和更快的運行。
StrutsTestCase 實(shí)踐用StrutsTestCase做Action的測試,我們創(chuàng )建一個(gè)類(lèi),這個(gè)類(lèi)繼承類(lèi)MockStrutsTestCase。這個(gè)類(lèi)提供了建立模擬HTTP請求的方法。用這個(gè)HTTP請求調用Struts action,并且當這個(gè)action完成時(shí)檢查應用程序的狀態(tài)。
假設,一個(gè)帶有符合查詢(xún)功能函數的在線(xiàn)預定賓館系統數據庫。
查詢(xún)函數是通過(guò)/search.do action類(lèi)實(shí)現。
這個(gè)Action類(lèi)體統一個(gè)基于特殊標準的符合查詢(xún)功能,將通過(guò)這個(gè)功能得到的結果列表放置在request范圍內的屬性“results”中。
比如:
下面的請求URL將會(huì )顯示所有法國的住宿地列表。
/search.do?country=FR現在,我們用測試驅動(dòng)的方法去實(shí)現這個(gè)方法。編寫(xiě)這個(gè)action類(lèi)并且更新Struts的配置文件。
我們也寫(xiě)一個(gè)測試類(lèi)測試這個(gè)aciton(空)類(lèi)。
用嚴格的測試驅動(dòng)的方法,首先,我們編寫(xiě)一個(gè)測試類(lèi),然后實(shí)現這個(gè)測試類(lèi)的測試代碼。實(shí)際情況下,標準的順序根據被測試的代碼不同而有所不同。
如下初始化測試類(lèi):
public void testSearchByCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
actionPerform();
}在這里,我們設置調用setRequestPathInfo()的路徑,增加了addRequestParameter()方法的請求參數。
接下來(lái),我們調用這個(gè)帶有actionPerform()方法的action類(lèi)。這樣會(huì )校驗Struts配置并調用相應的action類(lèi),但是不能測試這個(gè)acition實(shí)際的功能是什么。為了知道這個(gè)action類(lèi)的實(shí)際的功能,我們需要檢查核對action類(lèi)返回的結果。
public void testSearchByCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
actionPerform();
verifyNoActionErrors();
verifyForward("success");
assertNotNull(request.getAttribute("results"));
}這里我們核對三件事:
· 無(wú)ActionError消息(verifyNoActionErrors())
· 返回“success”轉向路徑。
· 結果集屬性被放置在request范圍內。
如果我們使用了tile,那么我們也要用以下的代碼核查“success”轉向是否指向正確的tile定義。
public void testSearchByCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
actionPerform();
verifyNoActionErrors();
verifyTilesForward("success",
"accommodation.list.def");
assertNotNull(request.getAttribute("results"));
}實(shí)際應用中,我們可能執行特殊業(yè)務(wù)邏輯的測試。
比如:
假設results屬性包含了100個(gè)賓館對象,我們要確認所有的這個(gè)results列表包含了賓館都是在法國。為了達到這個(gè)目的,我們需要做一個(gè)測試,所用的代碼與標準的JUnit測試代碼很相似。
public void testSearchByCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
actionPerform();
verifyNoActionErrors();
verifyForward("success");
assertNotNull(request.getAttribute("results"));
List results
= (List) request.getAttribute("results");
assertEquals(results.size(), 100);
for (Iterator iter = results.iterator();
iter.hasNext();) {
Hotel hotel = (Hotel) iter.next();
assertEquals(hotel.getCountry,
TestConstants.FRANCE);
...
}
}當你遇到更復雜的情況,你可能想測試一系列的action.
比如:
用戶(hù)做一個(gè)所有在法國的賓館的查詢(xún),單擊其中一個(gè)賓館以查看這個(gè)賓館的詳細信息。 假設,我們有一個(gè)用以顯示賓館詳細信息的action類(lèi),如下調用:
/displayDetails.do?id=123456使用StrutsTestCase,我們就能輕松的模擬上述的過(guò)程:先查詢(xún)一個(gè)在法國的所有賓館的列表,然后點(diǎn)擊進(jìn)去可以查看詳細的賓館信息。
如下:
public void testSearchAndDisplay() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
actionPerform();
verifyNoActionErrors();
verifyForward("success");
assertNotNull(request.getAttribute("results"));
List results
= (List) request.getAttribute("results");
assertEquals(results.size(),100);
Hotel hotel = (Hotel) results.get(0);
setRequestPathInfo("/displayDetails.do");
addRequestParameter("id", hotel.getId());
actionPerform();
verifyNoActionErrors();
verifyForward("success");
Hotel hotel
= (Hotel)request.getAttribute("hotel");
assertNotNull(hotel);
...
}測試Struts的錯誤處理測試struts的錯誤處理也是很重要的。
假設,如果當一個(gè)錯誤的國家代碼被定義的時(shí)候,我們的應用程序對此錯誤有很好的處理能力。
我們編寫(xiě)一個(gè)新的測試方法,此方法用verifyActionErrors()方法檢查返回的struts 錯誤信息。
public void testSearchByInvalidCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "XX");
actionPerform();
verifyActionErrors(
new String[] {"error.unknown,country"});
verifyForward("failure");
}有時(shí),我們想在A(yíng)ctionForm對象中直接進(jìn)行數據驗證。你可以按照如下的例子,使用getActionForm()方法。
public void testSearchByInvalidCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "XX");
actionPerform();
verifyActionErrors(
new String[] {"error.unknown,country"});
verifyForward("failure");
SearchForm form = (SearchForm) getActionForm();
assertEquals("Scott", form.getCountry("XX"));
}這里,我們驗證了在出現一個(gè)錯誤的國家代碼的時(shí)候,ActionForm能將此錯誤國家代碼很好的保存。
定制測試環(huán)境有時(shí),重載setUp()方法是有用的,它將讓你指定在無(wú)默認設置情況下的選項。
在這個(gè)例子中,我們使用不同的struts-config.xml文件,并且使得XML配置文件的驗證無(wú)效:
public void setUp() {
super.setUp();
setConfigFile("/WEB-INF/my-struts-config.xml");
setInitParameter("validating","false");
}第一級別的性能測試測試一個(gè)action或一系列action是測試請求、響應時(shí)機是否合理的一種優(yōu)秀的方法。通過(guò)Struts action測試,我們能夠檢查服務(wù)器端的性能(當然排除Jsp頁(yè)面)。在單元測試階段進(jìn)行一些初始級別的性能測試是快速排除和隔離性能問(wèn)題和避免在集成階段性能衰退的明智之舉。
這里是我用第一級別的Struts性能測試的一些基本規則:
· 盡可能的使用多的組合條件進(jìn)行符合條件的查詢(xún)。(以確認索引被正確的定義)
· 進(jìn)行大容量的查詢(xún)測試(此查詢(xún)返回大量的結果)用以確認響應的次數和結果頁(yè)面。
· 單獨和重復的測試結合(以檢查緩存的性能,如果緩存策略被使用)
一些開(kāi)源的實(shí)驗室幫助你進(jìn)行性能的測試,比如:Mike Clark JUnitPerf。當然,這樣的測試如果結合StrutsTestCase是有一定的困難的。在很多情況下,一個(gè)簡(jiǎn)單的計數器就是解決這個(gè)問(wèn)題的竅門(mén)。以下就是一個(gè)簡(jiǎn)單但有效的進(jìn)行第一級的新能測試的方法:
public void testSearchByCountry() {
setRequestPathInfo("/search.do");
addRequestParameter("country", "FR");
long t0 = System.currentTimeMillis();
actionPerform();
long t1 = System.currentTimeMillis() - t0;
log.debug("Country search request processed in "
+ t1 + " ms");
assertTrue("Country search too slow",
t1 >= 100)
}結論通常,單元測試是敏捷編程的一部分,測試卻動(dòng)開(kāi)發(fā)是其中特別的一部分。StrutsTestCase提供了一種簡(jiǎn)單有效的測試Struts action進(jìn)行單元測試的方法,而對Struts action進(jìn)行測試在Junit看來(lái)是非常困難的。