基于Junit2.0的StrutsTestCase应用 
  
       在我的前一篇文档《测试驱动的开发是重要的》中说过我要写一些测试框架应用方面的文档,今天我要实现我的诺言之一,这篇文章是介绍StrutsTeseCase的,熟悉并采用struts的开发员曾经一定有过这样一个困扰:我的action如何进行测试?(不是说要“测试先行”么?),如果没有一个可行的测试框架那我的struts环境去哪里模拟(方便的、透明的去模拟)?不要着急,接下来的部分我要向你们介绍这样一种可以满足我们要求的测试框架:strutstestcae。 
                                                                        ——写在前面 
  
主要内容介绍: 
1.  StrutsTeseCase是什么? 
2.  它的“家”在哪里? 
3.  如何让它来为我们工作?(伴随说明:我到底该实施“测试先行”?) 
4.  兼容struts1.1开发员 
5.  参考资源 
  
“由于在这里没有牵涉到Struts以及Junit入门的知识,所以我假定这篇文章的读者都是有struts开发经验的开发员并熟悉Junit。” 
  
第一部分:StrutsTestCase是什么? 
        
       StrutsTestCase是基于Junit的一个方便测试struts框架的测试框架。它提供模拟对象(Mock Object)和Cactus两种方式来“真实”的运行Struts ActionServlet,它允许你在不启动servlet 引擎的情况下测试你的struts代码。因为strutstestcase可以用ActionServlet来测试你的代码,所以它不光可以测试你的action,同时它也可以测试你的(容器中的?)mapping,frombeans以及forwards声明。我前面曾提到过它对我们开发员来说是“透明的”,因为象action,mapping,form beans 以及forward等等,我们真的可以象在常规的XXXAction中一样在我们的测试代码中随意的使用它们。 
  
       在最新的版本中它还提供了对tiles和多模块(struts1.1中的功能)的测试。 
  
       哇,是不是很奇妙,不要着急,我们很快就可以领略到的它的妙处。 
  
第二部分:它的“家”在哪里? 
  
       就象许许多多的开源项目一样,StrutsTestCase的家也在“sourceforge.org”(我们伟大的sourceforge就象一个繁忙的峰槽一样J),你可以通过http://sourceforge.net/project/showfiles.php?group_id=39190来下载它得最新版本。 
       JavaDoc: http://strutstestcase.sourceforge.net/api/index.html 
       热点论坛:http://sourceforge.net/forum/forum.php?forum_id=121751 
       常见问题:http://strutstestcase.sourceforge.net/faq.htm 
  
第二部分:如何让它来为我们工作? 
 “模仿测试(Mock Testing)VS 容器内测试(In-Container Testing)” 
  
      通常测试服务器端代码有两种比较常用的测试方法: 
  
      模仿对象(mock objects)它通过假设服务器端容器来达到测试效果; 
      容器内测试(in-container testing),它则是在真实的容器内达到测试效果; 
  
      而我们的StrutsTestCase则在对你的测试代码最小影响下能分别扮演上边两种角色。因此我们不得不说到它的这两种实现是如何完成的? 
  
       StrutsTestCase提供两种基类(他们分别继承标准的Junit TestCase): 
  
       MockStrutsTestCase: 
              通过名字也可以知道他是通过第一中方法在不启动servlet的条件下来模仿一些HttpServlet实现假设容器环境的。 
       CactusStrutsTestCase: 
              它是体现在容器内测试(真实环境测试)的,其通过另外一种测试框架(Cactus testing framework:http://jakarta.apache.org/cactus)struts代码。 
        
       Ps:本文中牵涉的代码都是通过第一中方法(继承MockStrutsTestCase)来完成测试的,要想用CactusStrutsTeseCase你只要简单的让测试代码继承CactusStrutsTeseCase即可。 
  
下面我们着重讲解MockStrutsTestCase是为我们工作的? 
  
首先我们先看看一个简单的LoginAction的简化代码: 
  
| 
 public class LoginAction extends Action { 
  
    public ActionForward perform(ActionMapping mapping, 
                                 ActionForm form, 
                                 HttpServletRequest request, 
                                 HttpServletResponse response) 
    { 
  
        String username = ((LoginForm) form).getUsername(); 
        String password = ((LoginForm) form).getPassword(); 
  
        ActionErrors errors = new ActionErrors(); 
  
        if ((!username.equals("Jplateau")) || (!password.equals("sandy"))) 
            errors.add("password",new ActionError("error.password.mismatch")); 
  
        if (!errors.empty()) { 
            saveErrors(request,errors); 
            return mapping.findForward("login"); 
        } 
  
        // store authentication info on the session 
        HttpSession session = request.getSession(); 
        session.setAttribute("authentication", username); 
  
        // Forward control to the specified success URI 
        return mapping.findForward("success"); 
  
}  |   
  
上边LoginAction完成一个简单的登陆意图,从client搜集登陆数据(用户名和密码),然后做一个验证,如果验证有误返回登陆页;如果登陆成功返回成功页(或业务工作平台)并把用户姓名放入session。 
  
那我们就从上边这个简单的程序入手: 
  
首先,我们应该创建一个测试用例TestLoginAction,其基本架子是这样的: 
(请记住此时上边LoginAction的代码你还没有写,并且struts_config.xml中的关于LoginAction的actionmapping也是没有的,这些东西我们要经过边测试边写,但一定是先写测试,天啊,什么都还没有我该怎样测试啊,不要急,且看下去,J) 
  
| 
 public class TestLoginAction extends MockStrutsTestCase { 
  
    public void setUp() { super.setUp(); } 
  
    public void tearDown() { super.tearDown(); } 
  
    public TestLoginAction(String testName) { super(testName); } 
  
    public void testSuccessfulLogin() {} 
}  |   
  
首先我们头脑总中有这样一个actionmapping(注意只是假设的): 
  
| 
 <action path=”/longin” type=”Jplateau.strutstestcase.LonginAction” 
                   scope="request" 
                   name="userForm"> 
<forward  name="success" path="/main.jsp"/> 
<forward  name="login" path="/login.jsp"/> 
</action>  |   
  
有了这样一个假设,我们就可以从测试代码入手: 
| 
 public class TestLoginAction extends MockStrutsTestCase { 
  
public void setUp() { 
 super.setUp(); 
 //这里做一些初始化的东西,譬如数据库连接等 
 } 
  
public void tearDown() { 
 super.tearDown(); 
 //这里关闭你在setup中开启的资源,如关闭数据库连接等  
} 
  
    public TestLoginAction(String testName) { super(testName); } 
  
public void testSuccessfulLogin() { 
  
//选择你要执行哪一个actionmapping?这里就用我们刚才做的假设材料 
this. setRequestPathInfo("/login"); 
  
//首先要初始化提交数据:用户名、密码 
this.addRequestParameter(“usrename”,”Jplateau”); 
this.addRequestParameter(“passwd”,”sandy”); 
//注意有了这个之后,你就可以从formbean中使用提交以后的用户名和密码数据了,下//面我会解释 
  
//好,初始化数据完成以后开始执行action中的execute(),很简单,掉用actionPerform() 
this. ActionPerform(); 
  
//严正返回是否正确?这里就用我们刚才做的假设材料 
this verifyForward(“success”);. 
  
//下面验证登陆成功以后session中是否有用户的名称? 
String expect_username_from_session=”Jplateau”; 
//这里需要插写内容,就是strutstestcase完全可以在测试代码中使用跟真实action 
//中一样的环境,譬如可以通过this.getActionForm()得到相应的ActionForm 
//可以通过this.getRequest()得到HttpRequest 
//可以通过this.getSession()得到HttpSession,如下: 
String actual_username_from_session=this.getSession().getAttribute(“authentication”); 
  
this. assertEquals(“”, expect_username_from_session, actual_username_from_session); 
} 
  
public void testFailureLogin(){ 
//登陆失败的测试在此就不写了 
  
} 
}  |   
  
  
上边是一个简单的测试代码,好,运行!他此时肯定是通不过的,首先我们的java代码还没有写,struts-config.xml还没有配,那么,现在你可以现在做这些事情:“用最简单的做法或代码让上边那个测试通过。” 
  
第三部分:兼容struts1.1开发员 
  
下面说写和struts1.1相关的内容:测试tiles和多模块! 
  
测试tiles 
假设我们的actionmapping中有这样forward到tiles的情况,如下: 
  
| 
 //这里有关struts中tiles的使用不做介绍,请参考相关资料 
<forward name="success"                     path="/userListLayout"/>  |   
  
同时tiles_defs.xml中有相应配置: 
  
| 
 //其中mainLayout是我在tiles定义的类型框架    
<definition name="userListLayout" extends="mainLayout"> 
      <put name="body" value="/main.jsp" /> 
  </definition>  |   
  
  
那么我们在测试代码中可以如下测试tiles 
| 
 // 
this. verifyTilesForward(“success”,”userListLayout”);  |   
  
测试多模块: 
  
关于多模块的测试我希望能在下面代码的注释部分给你一个大概的介绍: 
  
| 
 public class TestLoginAction extends MockStrutsTestCase { 
  
    public TestLoginAction(String testName) { super(testName); } 
  
public void testSuccessfulLogin() { 
   //“mymodule”为系统中某个单独模块的名称(文件夹的名称) 
setConfigFile("mymodule","/WEB-INF/struts-config-mymodule.xml"); 
  
//这个地方和前面单模块的例子有些差别,此处有两个参数,第一个参数是模块的 
//名称,第二个参数的意义和上边相同; 
//和单模块相比,就上边两处区别 
       this.setRequestPathInfo("/mymodule","/login.do"); 
       this.addRequestParameter("username","Jplateau"); 
       this.addRequestParameter("password","sandy"); 
       this.actionPerform(); 
       this.verifyForward("success"); 
  
String expect_username_from_session=”Jplateau”; 
  
String actual_username_from_session=this.getSession().getAttribute(“authentication”); 
  
this. assertEquals(“”, expect_username_from_session, actual_username_from_session); 
    } 
}  |   
  
  
参考资源: 
1.  http://strutstestcase.sourceforge.net 
(本篇文章基本上是沿用该篇文档的思路,英文好的可以阅读这篇文档。) 
2.  Kent Beck 《Test-Driven Development By Example》 
3. 本文首发于本人资料站点:http://plateau.sicool.com 
  
Jplateau 2003年11月12日星期三 写于广州精博  
 
  |