程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> StrutsTestCase簡化開發過程

StrutsTestCase簡化開發過程

編輯:關於JAVA

StrutsTestCase(STC)框架是一個開源框架,用來測試基於 Struts 的 Web 應用程序。 這個框架允許您在以下方面進行測試:

在 ActionForm 類中的驗證邏輯(validate() 方法)。

在 Action 類中的業務邏輯(execute() 方法)。

動作轉發(Action Forwards)。

轉發 JSP。

STC 支持兩種測試類型:

Mock 方法 —— 在這種方法中,通過模擬容器提供的對象(HttpServletRequest、 HttpServletResponse 和 ServletContext),STC 不用把應用程序部署在應用服務器中,就 可以對其進行測試。

Cactus 方法 —— 這種方法用於集成測試階段,在這種方法中,應用程序要部署在容器 中,所以可以像運行其他 JUnit 測試用例那樣運行測試用例。

示例應用程序

首先我們將逐步介紹示例 Struts 應用程序的創建,這個應用程序是測試的基礎。可以用 Struts 自帶的 struts-blank.war 或者自己喜歡的 IDE 來創建示例應用程序。示例應用程 序中有一個登錄頁面,用戶在這裡輸入用戶名和口令。如果登錄成功,用戶會被重定向到成 功頁面。如果登錄失敗,那麼用戶會被重定向到登錄頁面。

選擇本文頂部或底部的 Code 圖標可以得到本文附帶的源代碼。

Login.jsp 頁面

創建登錄頁面,如清單 1 所示:

清單 1. Login.jsp

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html:html>
<HEAD>
<%@ page language="java"contentType="text/html;
  charset=ISO-8859-1"pageEncoding="ISO-8859-1" %>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859- 1">
<TITLE>Login.jsp</TITLE>
</HEAD>
<BODY>
<html:form action="/login">
<html:errors/>
<H3>Login</H3>
   <TABLE border="0">
    <TBODY>
      <TR>
        <TH>User Name</TH>
        <TD><html:text property='userName' value='' /></TD>
        <TR>
        <TR>
         <TH>Password</TH>
         <TD><html:text property='password' value='' /></TD>
        </TR>
        <TR>
         <TD><html:submit property="submit"  value="Submit" /></TD>
         <TD><html:reset /></TD>
        </TR>
        </TBODY>
   </TABLE>
</html:form>
</BODY>
</html:html>

LoginActionForm.java 類

創建 LoginActionForm.java 類,如清單 2 所示:

清單 2. LoginActionForm.java

public class LoginActionForm extends ActionForm {
public ActionErrors validate(
    ActionMapping mapping,
    HttpServletRequest request) {
    ActionErrors errors = new ActionErrors();
    if (userName == null || userName.length() == 0)
       errors.add("userName", new ActionError ("username.required"));
    if (password == null || password.length() == 0)
       errors.add("password", new ActionError ("password.required"));
    if( isUserDisabled(userName))
       errors.add("userName",new ActionError("user.disabled"));
    return errors;
}
//Query USERDISABLED table to check if user account is disabled
public boolean isUserDisabled(String userName) {
    //SQL logic to check if user account is disabled
}
}

在 validate() 方法中,需要檢測用戶是否輸入了用戶名和口令,因為這些字段是必需的 。而且,還需要查詢 USERDISABLED 表,確認用戶的帳戶沒有被禁用。

LoginAction.java 類

接下來,要創建 LoginAction.java 類,如清單 3 所示:

清單 3. LoginAction.java 類

public class LoginAction extends Action {
    public ActionForward execute(
       ActionMapping mapping,
       ActionForm form,
       HttpServletRequest request,
       HttpServletResponse response)
       throws Exception {
if (isValidUser(loginForm.getUserName(), loginForm.getPassword())) {
          request.getSession().setAttribute(
             "userName",
             loginForm.getUserName());
          return mapping.findForward("success");
       } else {
          ActionErrors errors = new ActionErrors();
          errors.add("userName", new ActionError ("invalid.login"));
          saveErrors(request, errors);
          return new ActionForward(mapping.getInput());
       }
    }
//Query User Table to find out if userName and password combination  is right.
    public boolean isValidUser(String userName, String password) {
     //SQL Logic to check if username password combination is  right
    }
}

在這裡,execute() 方法用於驗證用戶名和口令是否有效。示例應用程序用 USER 表保存 用戶名和口令。如果用戶的憑證有效,則會在請求范圍內保存用戶名,並把用戶轉到登錄成 功頁面(Success.jsp)。

struts-config.xml 文件

創建 struts-config.xml 文件,如清單 4 所示:

清單 4. struts-config.xml 文件

<action-mappings>
       <action path="/login" type="com.sample.login.LoginAction"
       name="loginForm" scope="request" input="Login.jsp">
          <forward name="success" path="/Success.jsp"/>
       </action>
</action-mappings>

如果登錄不成功,那麼用戶會被重新定向到登錄頁面。

Success.jsp 頁面

創建 Success.jsp 頁面,如清單 15 所示:

清單 5. Success.jsp 頁面

<HTML>
<HEAD>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ page language="java" contentType="text/html; %>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859- 1">
<TITLE>Success.jsp</TITLE>
</HEAD>
<BODY>
<%
    String userName = (String)session.getAttribute("userName");
%>
Login Successful<br/>
<P>Welcome: <%=userName%> .</P>
</BODY>
<HTML>

在這裡,可從屬性范圍中讀取 userName 屬性,並用它來歡迎已經登錄的用戶。

使用模擬對象方式

模擬測試是對應用程序進行單元測試的流行方式。如果是初次接觸模擬測試方式,想了解 更多的內容,那麼請參閱參考資料。

設置模擬方式

要使用模擬方式,必須對示例應用程序做少許修改。首先要從編寫模擬測試開始:

把 strutstest-2.1.*.jar 和 junit3.8.1.jar 添加到 classpath。

把 WEB-INF 文件夾添加到 classpath。

創建 MockLoginTestAction 類,它擴展了 MockStrutsTestCase 類。

運行單元測試用例。

現在就完成了對環境的設置,可以開始編寫單元測試用例了。

空的用戶名或口令

首先,需要驗證用戶是否沒有輸入用戶名或口令,然後向用戶顯示適當的錯誤信息,並將 用戶重定向到登錄頁面。可以在 MockLoginTestAction 類中創建 testLoginActionFormError() 方法, 如清單 6 所示:

清單 6. testLoginActionFormError() 方法

public void testLoginActionFormError()throws Exception{
    setRequestPathInfo("/login");
    actionPerform();
    String[] actionErrors =  {"username.required","password.required"};
    verifyActionErrors(actionErrors);
    verifyInputForward();
}

在編寫 STC 測試用例時,要做的第一件事就是告訴 STC 要測試哪個 ActionMapping 類 ,在這裡要測試 LoginAction,它被映射到 struts-config.xml 文件中的 "/login" 路徑, 因此我們必須調用 setRequestPathInfo("/login")。默認情況下,STC 在 /WEB-INF/ 文件 夾中查找 struts-config.xml 文件。如果在 classpath 沒有這個文件,就必須用 struts- config.xml 文件的完整路徑調用 setConfigFile()。

現在可以執行測試用例了。首先要調用 actionPerform() 方法,把控制權傳遞給 Struts 框架,執行測試用例。一旦控制權從 actionPeform() 返回,就可以調用 verifyXXX() 方法 ,測試對程序的假設。在示例應用程序中,我們想測試一下,在沒有用戶名和口令的時候, 調用 LoginAction 映射是否會利用出錯信息 ActionErrors(用於 username.required 和 password.required)將用戶重定向到登錄頁面。verifyInputForward() 方法檢查這個事務 的結果是否把用戶重定向到動作映射的輸入屬性指定的頁面,在這個例子中,該頁面是 Login.jsp。

可以用 String 數組調用 verifyActionErrors(),該數組指出,作為這個事務的結果, 應當在請求范圍中設置哪些 ActionErrors。我們想設置 username.required、 password.required 和 ActionErrors,所以創建了一個 String 數組來保存這些出錯信息, 並把它們發送給 verifyActionErrors() 方法。

STC 模擬方式如何工作

ActionServlet 在 Struts 框架中是一個控制器 servlet。當容器得到請求時,會把請求 傳遞給 ActionServlet,由後者進行所有的請求處理。

STC 背後的基本想法是自行創建 ActionServlet 對象,而不是讓容器來創建它,然後再 調用對象上的適當方法。ActionServlet 在初始化時需要 ServletContext 和 ServletConfig 對象,在請求處理時需要 HttpServletRequest 和 HttpServletResponse 對 象。STC 創建這些類的模擬對象,並把它們傳遞給 Struts。

MockStrutsTestCase 是一個擴展了 junit.framework.TestCase 類的 JUnit 測試用例, 所以每個測試用例都會執行 setup() 方法。在 MockStrutsTestCase 對象的 setup() 方法 中,STC 創建 ActionServlet 對象和其他必需的模擬對象。

在調用 setRequestPathInfo() 或 addRequestParameter() 方法時,會調用模擬 HttpServletRequest 對象的適當方法。在 HttpServletRequest 的模擬實現中,會把這條信 息保存在適當的設置狀態。所以,如果調用 addRequestParameter("name","value"),模擬 的 HttpServletRequest 對象會保存它,然後,在 Struts 調用 request.getParameter ("name") 時,用 "value" 作為返回值。

在恰當地完成 HttpServletRequest 初始化之後,就可以調用 actionPerform() 方法把 控制權傳遞給 Struts。actionPerform() 方法調用 ActionServlet 的 doPost() 方法傳遞 HttpServletRequest 和 HttpServletResponse 的模擬實現。

在 ActionServlet 的 doPost() 方法中,處理請求的方式與其他 Struts 請求的處理方 式類似,區別是直到執行 ActionForward JSP 組件之前才停止請求處理。在這個階段,模擬 對象的狀態會被修改,以指出已經保存 ActionErrors 或 ActionMessages,或者指出由此生 成的 ActionForward 是什麼。

一旦控制權從 control returns from the actionPerform() 方法返回,就可以調用適當 的 verifyXXX() 方法(檢測模擬對象的狀態)來檢查各種假設是否成立。

測試禁用的用戶

LoginActionForm 類的 isUserDisabled() 方法存在一個問題。在這個方法中,是通過查 詢 USERDISABLED 表來找出用戶帳戶是否被禁用。但是在當前的環境下,我們不想把時間浪 費在設置和查詢數據庫上。

請記住,我們的目標是檢查應用程序的 Struts 部分,而不是檢查數據庫的交互代碼。為 了測試數據庫交互代碼,可以從若干個可用工具中選擇一個工具,例如 DBUnit。針對這一情 況的最佳方案應當是創建一個 LoginActionForm 類的子類,並重寫其中的 isUserDisabled () 方法。這個方法將根據輸入參數的值判斷是返回 true 還是返回 false。

比如在這個例子中,方法會一直返回 true,除非用 disabledUser 作為輸入參數調用它 。現在只應當在單元測試階段使用這個方法,而主程序 LoginActionForm 不應當知道這一點 。針對這個需求,我創建了 STCRequestProcessor,它擴展了 RequestProcessor。它允許向 Action 和 ActionForm 類中插入模擬實現。

要使用 STCRequestProcessor,需要修改 struts-config.xml,如清單 7 所示:

清單 7. struts-config.xml 文件

<controller>
    <set-property property="processorClass"  value="com.sample.util.STCRequestProcessor"/>
</controller>
</code>

這一行指出 Struts 用 STCRequestProcessor.java 作為 RequestProcessor。不要忘記 ,在容器中部署應用程序部署時要刪除這些行。

接下來是創建 LoginActionForm 的模擬類,如清單 8 所示:

清單 8. MockLoginActionForm.java 類

public class MockLoginActionForm extends LoginActionForm {
    public boolean isUserDisabled(String userName) {
       if (userName != null && userName.equals ("disableduser"))
          return true;
       return false;
    }
}

isUserDisabled() 方法檢查用戶名是否為 "disableduser"。如果是,則應當返回 true ;否則應當返回 false。

接下來要 創建一個測試用例,對禁用用戶進行測試,如清單 9 所示:

清單 9. testDisabledUser() 方法

public void testDisabledUser()throws Exception{
    STCRequestProcessor.addMockActionForm("loginForm",
    "com.sample.login.mock.MockLoginActionForm");
    setRequestPathInfo("/login");
    addRequestParameter("userName","disableduser");
    addRequestParameter("password","wrongpassword");
    actionPerform();
    verifyInputForward();
    String[] userDisabled ={"user.disabled"};
    verifyActionErrors(userDisabled);
}

STCRequestProcessor.addMockActionForm() 方法把 MockLoginActionForm 作為 LoginActionForm 的模擬實現插進來。addRequestParameter() 方法設置用戶名和口令這兩 個請求參數。一旦控制權從 actionPerform() 返回,就可以調用 verifyActionErrors() 驗 證是否利用 user.disabled 出錯信息將用戶重定向到輸出頁面。

測試無效登錄

測試用例要測試 LoginAction 類的 execute() 方法內部的業務邏輯。execute() 方法調 用同一個類的 isValidUser() 方法,該方法接下來會查詢 USER 表,查看用戶名和口令組合 是否有效。現在,因為我們不想在測試階段查詢真正的數據庫,所以要創建一個 LoginAction 類的模擬子類,重寫 isValidUser() 方法,如清單 10 所示:

清單 10. MockLoginAction.java 類

public class MockLoginAction extends LoginAction {
    public boolean isValidUser(String userName, String password)  {
       if( userName.equals("ibmuser") && password.equals ("ibmpassword"))
          return true;
       return false;
    }
}

如果用戶名是 "ibmuser",口令是 "ibmpassword",則 MockLoginAction 類的 isValidUser() 方法將返回 true。調用 STCRequestProcessor.addMockAction() 方法把 MockLoginAction 插入 LoginAction,如清單 11 所示:

清單 11. testInvalidLogin() 方法

public void testInvalidLogin()throws Exception{
    STCRequestProcessor.addMockActionForm("loginForm",
    "com.sample.login.mock.MockLoginActionForm");
    STCRequestProcessor.addMockAction("com.sample.login.LoginAction",
    "com.sample.login.mock.MockLoginAction");
    setRequestPathInfo("/login");
    addRequestParameter("userName","ibmuser");
    addRequestParameter("password","wrongpassword");
    actionPerform();
    String[] invalidLogin ={"invalid.login"};
    verifyActionErrors(invalidLogin);
    verifyInputForward();
}

在這個測試用例中,插入了 LoginAction 和 LoginActionForm 的模擬實現,避免數據庫 查詢,接著要設置用戶名和口令參數。在控制權從 actionPerform() 返回之後,就可以檢查 是否利用 "invalid.login" 這條出錯信息把用戶重定向到登錄頁面。

測試有效登錄

現在是時候來驗證在用戶輸入正確的用戶名和口令時,是否用成功頁面歡迎用戶,如清單 12 所示:

清單 12. testLoginActionFormError

public void testValidLogin() throws Exception{
    STCRequestProcessor.addMockActionForm("loginForm",
    "com.sample.login.mock.MockLoginActionForm");
    STCRequestProcessor.addMockAction("com.sample.login.LoginAction",
    "com.sample.login.mock.MockLoginAction");
    setRequestPathInfo("/login");
    addRequestParameter("userName","ibmuser");
    addRequestParameter("password","ibmpassword");
     actionPerform();
    verifyNoActionErrors();
    verifyForward("success");
}

這一代碼段首先在請求參數中把用戶名設置為"ibmuser",並把口令設置為 "ibmpassword",然後調用 actionPerform()。在執行 actionPerform() 方法時,需要調用 verifyForward() 方法,檢查用戶是否被重定向到成功頁面。它還調用了 verifyNoActionErrors() 方法,以驗證在這個事務中沒有出現 ActionErrors。

模擬的優勢與不足

使用模擬方式有一些優勢。這種方式比較快,因為不必為了每個更改而啟動和停止容器。 另一方面,因為沒有使用真正的容器,所以可能無法驗證監聽器或過濾器帶來的副作用。而 且,因為沒有執行 ActionForward JSP 組件,所以也無法發現 JSP 中的錯誤。

Cactus 方式

Cactus(容器內)是集成測試階段的一種流行測試方法。

Cactus 方式的設置

要設置 Cactus,需要將 cactus.1.6.1.jar 和 aspectjrt1.1.1.jar 復制到 classpath 中。

Cactus 需要在 Web 應用程序中配置兩個 servlet,所以必須在 web.xml 文件中聲明它 們,如清單 13 所示:

清單 13. web.xml

<servlet>
    <servlet-name<ServletTestRedirector</servlet-name>
    <display-name<ServletTestRedirector</display-name>
<servlet-class<org.apache.cactus.server.ServletTestRedirector</servlet- class>
</servlet>
<servlet>
    <servlet-name<ServletTestRunner</servlet-name>
    <display-name<ServletTestRunner</display-name>

<servlet- class<org.apache.cactus.server.runner.ServletTestRunner</servlet- class>
</servlet>
<servlet-mapping>
    <servlet-name<ServletTestRedirector</servlet-name>
    <url-pattern</ServletRedirector</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name<ServletTestRunner</servlet-name>
    <url-pattern</ServletTestRunner</url-pattern>
</servlet-mapping>

接下來要創建 cactus.properties 文件,並把它放在 classpath 中,如下所示:

cactus.contextURL = http://localhost:9080/sample1
cactus.servletRedirectorName = ServletRedirector

本文使用 WebSphere Studio 內置的測試環境來運行測試用例,所以可以從 http://localhost:9080/sample1 訪問示例應用程序。請確保把這個路徑修改成指向 Web 應 用程序實際部署位置的路徑。

接下來要創建一個類,擴展 CactusStrutsTestCase。因為在模擬和 Cactus 方式中可以 使用相同的測試用例,所以可以在這個類中復制 MockLoginActionTest 的內容。在選中的容 器中構建並部署這個應用程序。

最後,把 jdbc/ds1 配置成數據源。

STC Cactus 方法的工作原理

在使用 Cactus 測試應用程序的時候,必須把應用程序部署在 Web 容器中,還要在容器 外面用 JUnit 測試用例的形式運行 Cactus 測試用例。在運行 Cactus 單元測試時,它會為 類中的每個測試用例方法都創建並執行一個針對 URL 的HTTP 請求,URL 由 cactus.properties 文件中 cactus.contextURL 參數指定。

在示例應用程序的例子中,在執行 testDisableUser 時,會創建並執行以下請求:

http://localhost:9080/sample1/ServletRedirector? Cactus_TestMethod=testDisabledUser&Cactus_TestClass=
  com.sample.test.CactusLoginActionTest&Cactus_AutomaticSession=true&Cactus _Service=CALL_TEST

這個請求會調用 ServletTestRedirector servlet(作為示例 Web 應用程序的一部分部 署)。在 ServletTestRedirector 中,Cactus 從 Cactus_TestClass 請求參數中查找測試 用例類的名稱,並調用 Cactus_TestMethod 參數指定的方法。在執行這個方法之後,就會以 HTTP 響應的方式把結果返回 Cactus 測試類,這個類將執行一個外部容器。

此外,在 testDisabledUser() 方法中的 CactusStrutsTestCase 的容器內(in- container)版本得到控制時(在本文的示例中是 CactusLoginActionTest),STC 會調用 actionPerform() 方法,該方法將創建 ActionServlet、ServletContext 和 ServletConfig 對象的實例。STC 還在包裝器中包裝了當前的請求和響應。然後它調用 ActionServlet 的方 法 doPost(),該方法使用的參數是這些包裝的 ServletRequest 和 ServletResponse 對象 。然後 Struts 會像平常一樣處理請求。

通過使用 Cactus 方式,就可以調用 processRequest(true) 方法告訴 STC 驗證轉發 JSP,從而執行和測試轉發的 JSP,以確保不會拋出任何編譯和運行時錯誤。

一旦控制權從 actionPerform() 返回,就可以調用各種 verifyXXX() 方法檢驗假設是否 成立。

測試轉發 JSP 的錯誤

修改 testVaidLogin() 方法,測試 Success.jsp,保證它沒有編譯時錯誤或運行時錯誤, 如清單 14 所示:

清單 14. testValidLogin() 方法

public void testValidLogin() throws Exception{
STCRequestProcessor.addMockActionForm ("loginForm","com.sample.login.mock.MockLoginActionForm");

STCRequestProcessor.addMockAction ("com.sample.login.LoginAction","com.sample.login.mock.MockLoginAction");
    processRequest(true);
    setRequestPathInfo("/login");
    addRequestParameter("userName","ibmuser");
    addRequestParameter("password","ibmpassword");
    actionPerform();
    verifyNoActionErrors();
    verifyForward("success");
    }

還要修改 Success.jsp,添加以下幾行,讓它拋出 RunTimeException 異常:

<%
    throw new RuntimeException("test error");
%>

現在,當運行這個測試用例時,testValidLogin() 會創建並執行數據庫查找,檢查用戶 帳戶是否禁用,用戶名和口令是否有效。如果測試失敗,則表明在執行 Success.jsp 時遇到 了運行時錯誤。

Cactus 的優勢與不足

使用 Cactus 當然有優勢,但是困難也不少。從正面來說,它允許測試 JSP 頁面的編譯 和運行時錯誤,還允許測試數據訪問代碼。從負面來說,這種方式要求把應用程序部署在容 器中,然後每做一次修改都要啟動和停止容器,這使 Cactus 成為一種較慢的模擬方式。

結束語

單元測試提供了很多好處。除了讓人確信代碼按照設計的方式工作之外,測試還是造就優 秀文檔的原因。而且,在設計類和接口時,單元測試還提供了一個優秀的反饋機制。最後, 單元測試對於管理變化也很有幫助。如果在對代碼進行更改之後,代碼通過了所有單元測試 ,那麼就可以確信這些更改是安全的。

不幸的是,許多開發人員放棄了單元測試,因為他們要花太多時間來編寫測試代碼。但是 通過使用 STC 的模擬方式,可以把通常花費在設置特定領域(例如數據庫和容器)開發環境 上的大量時間節省下來。因為不必每次都重新啟動和停止容器,所以 STC 還有助於迅速測試 變化。一旦代碼穩定下來,能夠通過所有測試用例,那麼只要改變一下測試用例的父類,就 可以將它用於集成測試。在集成階段使用 Cactus 還允許您自動化集成測試過程。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved