Struts框架驱动( StrutsTestCase)
对于使用Struts框架开发的项目,大家一直抱怨单元测试难做,因为很多都是容器对象,需要构造大量的桩对象,同时我们知道Struts框架涉及MVC模式的三个部分,要想脱离WEB服务器把它驱动起来也不是件容易的事,StrutsTestCase不仅很好地完成了对Struts框架的驱动,也免除了构建大量桩的工作量。
StrutsTestCase工作原理就是实现了驱动MVC中的C(ActionServlet控制器)以达到驱动业务逻辑的目的,它能够实现以下功能
u 不需要EasyMock,帮你模似WEB窗器对象、容器环境
u 模拟客户端发来的请求
u 驱动控制器执行操作(Action),相应地会调用到你的业务逻辑(被测单元)
u 验证转发页面是否正确
能够验证的范围包括:Action对象、mappings 、form beans、forwards,如:formbean的validate验证逻辑、struts-config.xml中的配置、session的属性、是否转发到了正确的页面等。
StrutsTestCase基于JUnit框架,你的用例类只需要继承自MockStrutsTestCase类,该类的基类就是JUnit的TestCase类,所以实现用例的方式同JUnit。使用了StrutsTestCase,你就可以做到无需布署到Web服务器,就可以边写代码边做测试。
1) CLASSPATH设置
使用StrutsTestCase需要在CLASSPATH中加上以下文件的路径。
u StrutsTestCase自己的strutstest.jar文件(注意文件名可能含版本号)。
u JUnit,因为StrutsTestCase基于JUnit框架。
u Log4J,StrutsTestCase使用了Log4J的日志功能。
u Struts框架相关的JAR包,注意一定要有commons-collections.jar。
u 还要指定/WEB-INF/struts-config.xml的位置,因为StrutsTestCase需要读该文件的配置,只要指定到WEB-INF文件夹级别。
2) MockStrutsTestCase类
我们的用例类需要继承自MockStrutsTestCase类,该类基于TestCase,所以用例写作方法同JUnit,需要注意的是,如果需要覆盖setUp方法,一定要显示地调用super.setUp(),因为基类的setUp()做了重要的初始化工作。该类提供了较为全面的用于Struts测试的一系列方法。
以下的一个测试方法实现了对Action的驱动,除assertEquals方法外,其它方法都来自MockStrutsTestCase基类。
publicvoid testSuccessfulLogin()
{
//指定客户端来的两个请求参数和值
addRequestParameter("username", "deryl");
addRequestParameter("password", "radar");
//指定请求路径,StrutsTestCase会到struts-config.xml中找该Action的定义
setRequestPathInfo("/login");
//模似客户端来的请求,驱动Action执行(包括构建ActionForm对象)
actionPerform();
//Action执行过后验证是否转发到了正确的页面
verifyForward("success");
verifyForwardPath("/main/success.jsp");
//验证session的一个属性是否存在
assertEquals("deryl", getSession().getAttribute("authentication"));
//验证是否有error
verifyNoActionErrors();
}
最后,需要指出的是,有一些项目也在使用WebWork框架,WebWork与Struts的不同在于它将WEB容器对象与我们的业务逻辑代码进行了隔离,这样我们在对业务代码做单元测试的时候就免去了构建容器对象的麻烦。
4 服务器布署环境下的组件测试(Cactus)
前面我们谈到,在不需要布署到WEB服务器的环境下,借助Mock技术可以实现对组件的测试,但基于以下两点原因,我们可能还是需要将组件布署到真实的WEB服务器环境下进行测试。
u 构建测试桩很麻烦,很费工作量,有时也不太容易模拟。
u 模拟的环境毕竟不同于真实的环境,真实环境下的测试可能有不同的结果。
为此,我们可以选择Cactus,它能够测试的组件包括:Servlet、Filter、JSP TagLib、JSP、EJB等。测试是基于真实的服务器环境的,用例结构基于JUnit框架,并且能够与ANT集成实现自动启动服务器、布署和自动化测试,实现真正意义上的持续集成开发。
1) 测试用例结构
以下是一个用例的基本结构的例子。
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;
import org.apache.cactus.WebResponse;
publicclass TestSampleServlet extends ServletTestCase
{
public TestSampleServlet(String theName)
{
super(theName);
}
publicstatic Test suite()
{
returnnew TestSuite(TestSampleServlet2.class);
}
publicvoid beginDoSomething(WebRequest webRequest)
{
}
@Override
protectedvoid setUp() throws Exception
{
super.setUp();
}
//测试方法
publicvoid testDoSomething()
{
}
@Override
protectedvoid tearDown() throws Exception
{
super.tearDown();
}
publicvoid endDoSomething(WebResponse theResponse)
{
}
}
注意针对每个测试方法,可能分别对应有begin和end方法,Cactus提供有三种不同的TestCase基类:JspTestCase、ServletTestCase、FilterTestCase。下面说一下每个基类能够测什么。
u 测试Servlet以及任何使用了像HttpServletRequest,HttpServletResponse,……这样的对象的代码。可以使用ServletTestCase。
u 测试Filter以及任何使用了像FilterConfig,……这样的对象的代码。可以使用FilterTestCase。
u 测试JSP,可以使用ServletTestCase或JspTestCase。
u 测试Taglibs以及任何使用了像PageContext,……这样的对象的代码。可以使用JspTestCase。
u 测试EJB。可以使用ServletTestCase或JspTestCase或FilterTestCase。
2) 工作原理
以下是Cactus的工作原理图。
Cactus的测试代码有服务器端和客户端两个部分,他们协同工作。客户端与服务器的连接是通过称为Redirector Proxy的对象处理的。相同的用例类在客户端侧和服务器侧分别实例化,也就是说上面定义的用例方法有一部分运行在客户端;另一部分则运行在服务器端。
1、JUnit Test Runner调用YYYTestCase.runTest(),这个方法寻找beginXXX(ServletTestRequest )。
2、YYYTestCase.runTest()打开一个到Redirector Proxy的HTTP连接。
3、Redirector Proxy进行如下操作。
u 创建Test class的实例。
u 创建一些Server对象(HttpServletRequest、ServletConfig、ServletContext)的Cactus包装器对象(wrapper)。
u 如果需要,创建一个HTTP Session。
4、Redirector Proxy通过反射技术,执行Test类的setUP()、testXXX()、tearDown()。
5、testXXX()调用服务器侧类的方法,并通过JUnit的assert API来验证测试结果。
6、如果测试失败,testXXX()方法抛出例外,Redirector Proxy会捕获例外。
7、Redirector Proxy向客户端返回例外的有关信息,JUnit会将这些信息打印出来。
8、如果没有发生例外,YYYTestCase.runTest()寻找并执行endXXX(WebResponse),在这儿你可以使用JUnit asserts检查返回的HTTP Header、Servlet输出流(stream)。
必须指出的是,endXXX方法有两个原型,以下是例子代码。
public void endXXX(org.apache.cactus.WebResponse theResponse) {
String content = theResponse.getText();
assertEquals(content, "<html><body><h1>Hello world!</h1></body></html>");
}
public void endXXX(com.meterware.httpunit.WebResponse theResponse) {
WebTable table = theResponse.getTables()[0];
assertEquals("rows", 4, table.getRowCount());
assertEquals("columns", 3, table.getColumnCount());
assertEquals("links", 1, table.getTableCell(0, 2).getLinks().length);
}
第二个方法原型中的com.meterware.httpunit.WebResponse类来自HttpUnit,也就是说Cactus集成了HttpUnit,以例更加全面地对返回的页面进行验证。