`
leon0122
  • 浏览: 45304 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

测试驱动开发with Junit(三)

阅读更多

第三章:使用mork进行测试开发:
       什么是mork?简单地说mork就是模型,模拟我们测试时需要的对象及测试数据。比如,用过Struts的朋友都知道,Struts中的action类要运行必须依靠服务器的支持,只有服务器可以提供HttpServletRequest,HttpServletResponse对象,如果不启动服务器,那么就没有办法对action类进行单元测试(当然了,使用mock测试除外)。对struts的Action进行测试是很困难的。即使当业务逻辑很好的被限定在业务层,Struts action通常还是会包含很重要的数据验证、数据转换和数据流控制代码。不对Struts action 支路进行测试在代码的覆盖率上会有很多的不足。单单依靠启动服务器运行程序来测试action太过于麻烦。如果让action脱离容器,那么测试就变得极为简单。脱离了容器,request与response对象如何获得?这时可以使用mork来模拟request与response对象,这样测试 action类就会变得极为简单。下面就简单的介绍一下如何使用StrutsTestCase来进行action的测试。

StrutsTestCase介绍
       StrutsTestCase工程提供了一种在JUnit框架下测试struts action的灵活、便利的方法。你可以通过设置请求参数,检查在Action被调用后的输出请求或Session状态这种方式对Struts Action做白盒测试。StrutsTestCase 提供了用框架模拟web容器的模拟测试方法也提供了真实的web容器(比如:Tomcat)下的测试方法。所有的StrutsTestCase单元测试类都源于模拟测试的 MockStrutsTestCase或容器内测试的CactusStrutsTestCase。
       下面的例子就是使用mock测试,因为它需要更少的启动和更快的运行。
要使用StrutsTestCase请在下列地址下载最新的发行包
http://sourceforge.net/project/showfiles.php?group_id=39190

      下面就开始我们的action测试之旅。首先先让我们了解一下要实现的程序的功能:在index.jsp中输入班级编号以列表形式在result.jsp页面中显示相关班级学员信息
在result.jsp中点击学员姓名在info.jsp中显示该学生的详细信息。
(注:这里数据库查询使用基本的jdbc实现,我们假设DAO层已经通过测试,因为这不是我们这里关注的重点,数据库使用SqlServer2000,同时假定你已经具备使用eclipse开发struts的相关知识)
       在pubs数据库中新建一个student表,在表中插入如下测试数据。

 
  1. use pubs        
  2. go        
  3.         
  4. create table student        
  5. ([id] int primary key identity(1,1),        
  6. stuName varchar(20) not null,        
  7. stuAge int not null,        
  8. stuSex varchar(20)not null,        
  9. stuClass varchar(20) not null       
  10. )        
  11. go        
  12.         
  13. insert into student values('jack',23,'man','GS2T11')        
  14. insert into student values('mohindar',24,'man','GS2T11')        
  15. insert into student values('tim',21,'man','GS2T11')        
  16. insert into student values('rose',23,'woman','GS2T11')        
  17. insert into student values('sakura',22,'woman','GS2T11')        
  18.      
  19. insert into student values('dess',23,'man','GS2T12')        
  20. insert into student values('hiruku',24,'man','GS2T12')        
  21. insert into student values('lucy',21,'woman','GS2T12')        
  22. insert into student values('liky',23,'woman','GS2T12')        
  23. insert into student values('desr',22,'woman','GS2T12')        
  24. go        


       先让我们来看看第一个功能如何实现:
  
       在search.jsp中填入数据,然后将其提交给SearchAction类来处理。SearchAction类调用业务逻辑层查询,将结果保存至 request对象的属性"result"中,并且跳转至info.jsp页面。如果查询不到结果,就构造一个ActionMessage,封装错误信息,并转向到error.jsp页面。
      下面的请求URL将会显示调用SearchAction类:
      search.do?method=search
      具体步骤如下:
      步骤1:在eclipse中放入strutstests-2.1.3.jar,junit.jar、commons-collections.jar
    
      步骤2:构造一个名为SearchForm的ActionForm和一个名为SearchAction 的action。SearchForm的属性为:clazzName

      步骤3:更改相应的配置文件

 
  1. <form-beans>  
  2.     <form-bean name="searchForm" type="com.struts.form.SearchForm" />  
  3. form-beans>  
  4.   
  5. <action-mappings>  
  6.     <action attribute="searchForm" input="/search.jsp"  
  7.         name="searchForm" parameter="method" path="/search" scope="request"  
  8.         type="com.struts.action.SearchAction" validate="false">  
  9.         <forward name="error" path="/error.jsp" />  
  10.         <forward name="success" path="/result.jsp" />  
  11.     action>  
  12. action-mappings>  

     步骤4:编写测试类
构造一个名为SearchActionTest的类并继承MockStrutsTestCase。在该类中添
加测试方法testSearchSuccess

 
  1. import servletunit.struts.MockStrutsTestCase;        
  2.        
  3. public class SearchActionTest extends MockStrutsTestCase        
  4. {        
  5.    public void testSearchSuccess()        
  6.    {        
  7.       setRequestPathInfo("/search.do");          //1行        
  8.       addRequestParameter("method""search");  //2行        
  9.       actionPerform();                          //3行        
  10.   }        
  11. }       


       代码解释:
       1行:设置请求SearchAction类的请求路径
       2行:在请求路径后加上一个请求参数
                1行与2行就构成了/search.do?method=search
       3行:调用actionPerform()来执行action类
       注:以上代码就是对一个测试方法的初始化。可以根据需要自行修改

       步骤5:根据struts-config.xml文件完善testSearch方法。先验证可以按照班级名称查询出所需要的数据,主要做以下事情:
       a)   初始化提交数据
       b)   验证request中"result"属性是否有值
       c)   是否返回result.jsp页面    
       代码如下:

 
  1. public class SearchActionTest extends MockStrutsTestCase        
  2. {        
  3.     public void testSearchSuccess()        
  4.     {        
  5.         setRequestPathInfo("/search.do");        
  6.         addRequestParameter("method""search");        
  7.              
  8.         addRequestParameter("clazzName","gs2t11");      //1行        
  9.         actionPerform();        
  10.              
  11.         assertNotNull(request.getAttribute("result"));  //2行        
  12.         verifyForward("success");                       //3行        
  13.     }        
  14. }      

       代码解释:
       1行:添加提交数据,该数据就相当与从search.jsp提交的数据
       2行:查询成功后,将结果放入request中,所以判断result中的"result"属性是否
                 有值来断定是否将结果成功放入request对象中。
       3行:验证查询成功后是否会返回success.jsp页面
       运行junit,如果出现以下错误(具体如下图):(请参考附件吧。这里所有的图都省略……)
       那么请在src下建立一个名WEB-INF的目录,将WebRoot下的WEB-INF中的web.xml文件与struts-config.xml文件拷贝过去即可。如图:


       步骤6:在SearchAction中用最简单的代码先去实现步骤5中的功能
       代码如下:           

 
  1. public class SearchAction extends DispatchAction   
  2. {   
  3.     private SearchHandler sh;   
  4.   
  5.     public ActionForward search(ActionMapping mapping,ActionForm    
  6.               form, HttpServletRequest request, HttpServletResponse response)   
  7.     {   
  8.         SearchFormsearchForm = (SearchForm) form;   
  9.         //获取表单中输入的班级   
  10.         String className = searchForm.getClazzName();   
  11.            
  12.         //查询该班级的信息   
  13.         sh = new SearchHandler();   
  14.         List list=sh.searchByClassName(className);   
  15.            
  16.         if(list!=null)   
  17.         {   
  18.             //将班级信息保存至request中,并返回result.jsp   
  19.             request.setAttribute("result",list);   
  20.             return mapping.findForward("success");   
  21.         }   
  22.         else  
  23.             return null;   
  24.     }   
  25. }  

           注释比较详细,就不再多解释了
        运行测试类结果如下(成功部分编写完毕)

        步骤7:编写不能成功取出数据的测试。在SearchActionTest类中添加一个名为testSearchFail的方法
       代码如下:      

 
  1. public void testSearchFail()   
  2. {   
  3.     setRequestPathInfo("/search.do");   
  4.     addRequestParameter("method""search");   
  5.            
  6.     addRequestParameter("clazzName","gs2txx");       //1行   
  7.     actionPerform();   
  8.            
  9.     verifyActionMessages(new String[] {"no.stuclass.found"});//2行   
  10.     verifyForward("error");                            //3行   
  11.            
  12.     verifyForwardPath("/error.jsp");                              //4行   
  13. }  

       代码解释:
       1行:输入一个不存在的班级
       2行:验证向ActionMessages中放入的消息文本
       3行:验证返回地址
       4行:验证页面地址是否与转向的error对应

      步骤8:更改SearchAction类,实现步骤7的功能
      代码如下:(粗体为增加部分)

 
  1. public class SearchAction extends DispatchAction   
  2. {   
  3.     private SearchHandler sh;   
  4.   
  5.     public ActionForward search(ActionMapping mapping,ActionForm    
  6.              form, HttpServletRequest request, HttpServletResponse response)   
  7.     {   
  8.         SearchFormsearchForm = (SearchForm) form;   
  9.         //获取表单中输入的班级   
  10.         String className = searchForm.getClazzName();   
  11.            
  12.         //查询该班级的信息   
  13.         sh = new SearchHandler();   
  14.         List list=sh.searchByClassName(className);   
  15.            
  16.         if(list!=null)   
  17.         {   
  18.             //将班级信息保存至request中,并返回result.jsp   
  19.             request.setAttribute("result",list);   
  20.             return mapping.findForward("success");   
  21.         }   
  22.         else  
  23.         {   
  24.              //构造一个ActionMessage保存一条消息   
  25.              ActionMessages messages=new ActionMessages();   
  26.              messages.add(Globals.MESSAGE_KEY, new ActionMess("no.stuclass.found")); 
                 saveMessages(request,messages);   
  27.              //返回error.jsp   
  28.              return mapping.findForward("error");   
  29.         }   
  30.     }   
  31. }  


       运行测试类,正确

       至此第一个功能测试开发完毕
       接着我们一起来完成第二个功能:
                      
       在result.jsp页面中,点击学生的姓名后将其提交给DisplayAction类来处理。DisplayAction类调用业务逻辑层查询,将结果保存至request对象的属性"stu"中,并且跳转至info.jsp页面
       下面的请求URL将会显示调用SearchAction类:
        display.do?method=display&stuName=jack
       具体步骤如下:
       步骤1:构造一个名为DisplayForm的ActionForm和一个名为DisplayAction的Action。DisplayForm的属性为stuName
       步骤2:修改相应的struts-config.xml

 
  1. <form-beans>  
  2.     <form-bean name="displayForm" type="com.struts.form.DisplayForm"/>  
  3. form-beans>  
  4.   
  5. <action-mappings>  
  6.     <action attribute="displayForm"    
  7.         name="displayForm" parameter="method" path="/display" scope="request"  
  8.         type="com.struts.action.DisplayAction" validate="false">  
  9.         <forward name="info" path="/info.jsp" />  
  10.     action>  
  11. action-mappings>  

       步骤3、编写DisplayAction的测试类DisplayActionTest,在其中加入一个名为testShowInfo()的方法。该方法完成以下事情:
       a) 初始化提交数据
       b) 验证request中"stu"属性是否有值
       c) 是否返回info.jsp页面
       代码如下:     

 
  1. public class DisplayActioTest extends MockStrutsTestCase   
  2. {   
  3.     public void testShowInfo()   
  4.     {   
  5.         setRequestPathInfo("/display.do");   
  6.         addRequestParameter("method","display");   
  7.         addRequestParameter("stuName""jack");   
  8.            
  9.         actionPerform();   
  10.            
  11.         assertNotNull(request.getAttribute("stu"));   
  12.         verifyForward("info");   
  13.            
  14.     }   
  15. }  

 

        步骤4:更改DisplayAction类,用最简短的代码实现其功能
       代码如下:

 
  1. public class DisplayAction extends DispatchAction   
  2. {   
  3.     private SearchHandler sh;   
  4.     public ActionForward display(ActionMapping mapping, ActionForm form,     
  5.                      HttpServletRequest request, HttpServletResponse response)   
  6.     {   
  7.         sh=new SearchHandler();   
  8.         DisplayForm displayForm = (DisplayForm) form;   
  9.         String name=displayForm.getStuName();   
  10.            
  11.         Student stu=sh.displayByName(name);   
  12.         request.setAttribute("stu",stu);   
  13.            
  14.         return mapping.findForward("info");   
  15.     }   
  16. }  

      
       步骤5:运行测试类,结果正确

       至此,第二个功能测试开发完毕
       经过前面两个功能的测试与运行,两个功能都可以单独测试通过,由于第二个功能是以第一个功能成功查询为前提才能运行,所以再编写一个测试,将两个 action功能整合。这样就可以测试整个功能模块是否正确。在DisplayActioTest中编写一个名为 testSearchAndDisplay()的方法来完成这个测试吧。

       代码如下:

 
  1. public void testSearchAndDisplay()   
  2. {   
  3.     //先调用SearchAction来取得数据   
  4.     setRequestPathInfo("/search.do");   
  5.     addRequestParameter("method""search");   
  6.            
  7.     addRequestParameter("clazzName","gs2t11");   
  8.     actionPerform();   
  9.            
  10.     List list=(List) request.getAttribute("result");   
  11.     Student student=(Student) list.get(0);   
  12.            
  13.     //调用DisplayAction类显示在result.jsp中选择的数据   
  14.     setRequestPathInfo("/display.do");   
  15.     addRequestParameter("method","display");   
  16.     addRequestParameter("stuName", student.getStuName());   
  17.            
  18.     actionPerform();   
  19.   
  20.     assertNotNull(request.getAttribute("stu"));   
  21.     verifyForward("info");   
  22. }  


        运行测试类,测试成功

        整个模块到这里全部开发测试完毕,但是还没完,作为测试驱动开发最重要的一环就是重构,这样才能保证你程序的健壮性、可读性、可维护性及优雅性。这里重构主要是针对Action类进行重构,你也可以用同样的方法在编写完测试代码之后对测试代码进行重构。这里主要针对以下几个地方进行重构
        1)   抽取Action类中的变量:

               SearchAction类中:

 
  1. SearchForm searchForm= (SearchForm) form;     
  2. String className = searchForm.getClazzName();     
  3. sh = new SearchHandler();     
  4. List list=sh.searchByClassName(className);    


               可以用下列代码替换:

 
  1. sh = new SearchHandler();     
  2. List list=sh.searchByClassName(((SearchForm) form).getClazzName());    


                DisplayAction类中:

 
  1. DisplayForm displayForm = (DisplayForm) form;     
  2. String name=displayForm.getStuName();     
  3. Student stu=sh.displayByName(name);     
  4. request.setAttribute("stu",stu);    


               可以用下列代码替换:

 
  1. request.setAttribute("stu",sh.displayByName(((DisplayForm)form).getStuName()));    

        2)  抽取常量:

               SearchAction类中:

 
  1. request.setAttribute("result",list);      

               可以用下列代码替换

 
  1. private static final String RESULT = "result";     
  2. request.setAttribute(RESULT,list);    

              SearchAction类中

 
  1. request.setAttribute("stu",sh.displayByName(((DisplayForm) form).getStuName()));    

              可以用下列代码替换

 
  1. private static final String STUDENT = "stu";     
  2. request.setAttribute(STUDENT,sh.displayByName(((DisplayForm) form).getStuName()));    

        构造一个名为BaseAction的类,继承DispatchAction类,并在其中初始化一个SearchHandler对象

 
  1. public class BaseAction extends DispatchAction     
  2. {     
  3.     protected SearchHandler sh=new SearchHandler();     
  4. }    


        SearchAction类与DisplayAction类可改为

 
  1. public class SearchAction extends BaseAction     
  2. public class DisplayAction extends BaseAction    


        同时要去掉两个类中的SearchHandler属性

        SearchAction类中下列代码可以抽取成方法:

 
  1. ActionMessages messages=new ActionMessages();     
  2. messages.add(Globals.MESSAGE_KEY, new ActionMessage("no.stuclass.found"));     
  3. saveMessages(request,messages);    


        可以用下列代码替换:

 
  1. addResultMessage(request,"no.stuclass.found");   
  2.   
  3. private void addResultMessage(HttpServletRequest request,String message)   
  4. {   
  5.      ActionMessages messages=new ActionMessages();   
  6.      messages.add(Globals.MESSAGE_KEY, new ActionMessage("no.stuclass.found"));   
  7.      saveMessages(request,messages);   
  8. }  


       由于在别的Action中也要经常生成ActionMessages对象,所以再将该方法 抽取到BaseAction中。

       最后BaseAction类代码如下:

 
  1. public class BaseAction extends DispatchAction   
  2. {   
  3.     protected SearchHandler sh=new SearchHandler();   
  4.   
  5.     protected void addResultMessage(HttpServletRequest request, String message)   
  6.     {   
  7.         ActionMessages messages=new ActionMessages();   
  8.         messages.add(Globals.MESSAGE_KEY, new ActionMessage(message));   
  9.         saveMessages(request,messages);   
  10.     }   
  11. }  

        最后代码效果请参考源代码。

        4)    重构注意事项:

               a) 每次要一点点的重构,万万不要一次重构很多地方。

               b) 每重构完一个地方要运行测试类来保证其正确性。

               c) 不要怕麻烦。在后期重构的优势就会越来越明显。

       到此为止,全部的测试与代码编写已经结束。还等什么,将已经做好的页面套上去吧,感受一下不用启动服务器来开发web应用程序的乐趣吧,感受测试驱动开发给你带来的开发思想的质的飞跃……

分享到:
评论
2 楼 jiapumin 2010-09-27  
学习了,很实用的
1 楼 chian_xxp 2009-09-29  
在你这里,学习到我想要的东西。谢谢。

相关推荐

    测试驱动开发With Junit

    总结,测试驱动开发With Junit涉及到的主要知识点包括: - TDD(测试驱动开发)的概念和流程 - JUnit作为Java的单元测试框架,如何创建测试类和测试方法 - 如何使用`assert`方法进行断言,确保代码行为正确 - 创建和...

    JUnit 4测试驱动开发----junit技术讲解

    本文将深入讲解JUnit 4的关键特性和如何进行测试驱动开发。 1. 预备知识 在开始使用JUnit 4进行测试之前,了解一些预备知识是必要的。例如,Java 5引入的可变长参数(Variable-Length Arguments)允许函数接收不定...

    Pragmatic Unit Testing in Java with JUnit源码

    《Pragmatic Unit Testing in Java with JUnit》是一本专注于Java单元测试实践的书籍,其配套源代码提供了丰富的示例和实践案例,帮助读者深入理解如何有效地使用JUnit进行单元测试。JUnit是Java领域最广泛使用的...

    Junit 单元测试完整案例

    2. `@RunWith(SpringRunner.class)`:这是一个JUnit runner,它使得Spring TestContext Framework可以驱动测试执行。 3. `@SpringBootTest`:这个注解用于启动一个Spring应用上下文,可以指定配置类、web环境等。 ...

    junit4测试源码

    这个"junit4测试源码"可能包含了JUnit4框架的源代码,使得用户能够深入理解其内部工作原理,便于自定义扩展或学习测试驱动开发(TDD)的最佳实践。 首先,JUnit4引入了注解(Annotation)的概念,使得测试类和测试...

    软件测试与junit实践Java测试

    10. **TDD(测试驱动开发)**:JUnit也支持采用TDD(Test-Driven Development)模式,即先写测试,再编写能通过测试的代码,这有助于提高代码质量和可维护性。 总之,JUnit作为Java的主流测试工具,其强大功能和...

    软件测试与Junit实践 pdg

    在实际项目中,应当结合TDD(测试驱动开发)理念,先编写测试再实现功能,确保代码质量从一开始就得到保障。 总之,软件测试是保障软件质量的重要手段,而Junit作为Java的主流单元测试框架,为开发者提供了强大的...

    pragmatic unit testing in java with junit

    《Pragmatic Unit Testing in Java with JUnit》是一本专注于Java单元测试的指南,它深入讲解了如何有效地利用JUnit框架进行测试驱动开发(TDD)。JUnit是Java领域中最广泛使用的单元测试框架,它为开发者提供了编写...

    junit4 单元测试源码

    【标题】"junit4 单元测试源码"涉及的是Java编程中单元测试的重要工具...通过分析和运行这些源码,学习者可以掌握单元测试的基本概念,了解如何编写有效的测试用例,以及如何利用Eclipse的集成环境进行测试驱动开发。

    Pragmatic Unit Testing In Java With Junit

    - **Al Koscielny**(软件开发者):“如果在我开始进行测试驱动开发时就有这本书就好了。” - **Andrew Thompson**(顾问,Greenbrier & Russel):“虽然我对测试并不陌生,但我仍然在很多方面感到困惑。我认为这...

    Junit单元测试指南

    Junit单元测试是Java开发中的一个关键组成部分,它允许开发者对代码进行小规模的验证,确保每个函数或方法按照预期工作。本指南将深入探讨Junit的使用,帮助你掌握如何编写、运行和理解单元测试。 一、Junit简介 ...

    xUnit for Matlab with JUnit-compatible XML output.zip

    使用这个工具,开发者可以在Matlab中实现类似Java的测试驱动开发(TDD)或行为驱动开发(BDD)实践,从而提升代码质量和维护性。同时,通过JUnit兼容的XML输出,可以方便地将Matlab项目集成到更广泛的软件开发流程中。

    用Junit进行单元测试junit4.5

    1. **注解驱动(Annotation-Based)**:JUnit 4.5引入了注解来声明测试方法和类,如`@Test`、`@Before`、`@After`等,这使得测试代码更加简洁易读。 2. **参数化测试(Parameterized Tests)**:`@RunWith...

Global site tag (gtag.js) - Google Analytics