第三部分 testing components
从JUnit学习笔记1到11都是讲基本的测试尝试,懂得前面的知识,就可以开始Junit in action的最后一程了---测试组件。
第三部分让你把在第一第二部分学到的测试知识实践于J2EE组件。我们将重点学习为各种J2EE组件编写单元测试,并能学习如何用Ant、Maven和Eclipse这些工具进行测试。
第九章 对servlet和filter进行单元测试 Unit-testing servlets and filters
本章内容
- 示范测试驱动开发(TDD)方法
- 用Cactus和mock objects编写servlet和filter的单元测试程序
- 用Maven运行Cactus测试程序
- 选择何时用Cactus,何时用mock objects
介绍管理应用程序
这个示例管理就是目的是要让管理者对关系数据库进行查询。下图就是典型的web应用框架:
应用程序首先接受用户包含执行SQL查询的HTTP请求。请求被一个安全filter捕获,该安全filter是用来检查SQL查询是否是一个SELECT查询。如果不是,用户将被定向到一个错误的页面,如果查询是一个SELECT语句,那么就将调用AdminServlet。该servlet执行请求的数据库查询,并将结果传送到JSP显示页面。
用Cactus编写servlet测试
在这部分的内容中,我们重点是进行AdminServlet部分执行单元测试。在写代码之前就开始写测试程序叫驱动测试开发或测试先行策略。
先来分析一下AdminServlet的需求,该servlet从Http请求中提取包括将要执行命令的需用参数。然后它将使用提取的命令来获取数据。最后将控制权交给JSP页面来显示穿过来的数据。我们把对应于这些行为的方法分别称为getCommand、executeCommand和callView。
设计第一个测试
下面的代码给出了对getCommand方法的测试。
package junitbook.servlets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.servlet.ServletException; import org.apache.cactus.ServletTestCase; import org.apache.cactus.WebRequest; import org.apache.commons.beanutils.BasicDynaClass; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaProperty; public class TestAdminServlet extends ServletTestCase { public void beginGetCommandOk(WebRequest request) ;测试有效的情况 { ;定义了command request.addParameter("command", "SELECT..."); ;作为Http的参数 } ; ; public void testGetCommandOk() throws Exception ; { ; AdminServlet servlet = new AdminServlet(); ; String command = servlet.getCommand(request); ; ; assertEquals("SELECT...", command); ; } public void testGetCommandNotDefined() ;测试无效的情况,没有定义命令参数 { AdminServlet servlet = new AdminServlet(); try { servlet.getCommand(request); fail("Command should not have existed"); } catch (ServletException expected) { assertTrue(true); } } }
能让TestAdminServlet编译通过的最简单的代码
package junitbook.servlets; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; public class AdminServlet extends HttpServlet { public String getCommand(HttpServletRequest request) throws ServletException { return null; } }
在你继续其它测试和实现满足测试的最小应用之前,设法运行Cactus测试。虽然是失败的,但是你至少知道为什么会失败。 然后,当你实现测试中的代码时,测试将会成功!
-----------------------------------------------------------------------------------------------
-JUnit最佳实践 记得检验测试在应当失败的时候失败---------------------------------------------------
使用Maven来运行Cactus测试
在前面我们已经学习了使用Cactus/Jetty集成,从一个IDE中来进行Cactus测试。这次,我们将学会使用Maven Cactus插件在Tomcat中进行测试。Tomcat是众所周知的servlet/JSP引擎。Maven Cactus插件是运行Cactus测试最容易的方法之一。所有的事物对于使用者来说都是自动和透明的:你的应用文件war文件的“cactus”化、启动你的容器、配置“cactus”化的war,执行Cactus测试,及停止你的容器。(“cactus化”就是自动附加Cactus jars,并且把Cactus需要的入口附加在你的web.xml文件中)。
默认情况下,Maven Cactus插件在src/testcactus目录下查找Cactus测试程序。我们也需要将所有的元数据和资源文件放在src/webapp目录下。管理应用程序的web.xml文件如下:
1.0" encoding="ISO-8859-1"?> -//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> AdminServlet <servlet-class>junitbook.servlets.AdminServletclass> AdminServlet /AdminServlet
要用Maven运行Cactus测试,我们还要提供一个有效的project.xml文件,代码如下,关于细节,在前面的笔记中有描述:
1.0" encoding="ISO-8859-1"?> 3 junitbook-servlets JUnit in Action - Unit-testing servlets and filters 1.0 Manning Publications Co. http://www.manning.com/ http://www.manning.com/front/dance.gif 2002-2003 <package>junitbook.servletspackage> /images/jia.jpg Chapter 9 shows how to unit test Servlets annd Filters using both the Mock Object approach and the in-container approach. It highlights how they complement each other and give strategies on when to use them. Chapter 9 of JUnit in Action: Unit-testing servlets and filters http://sourceforge.net/projects/junitbook/ Vincent Massol vmassol vmassol@users.sourceforge.net Pivolis Java Developer commons-beanutils commons-beanutils 1.6.1 true servletapi servletapi 2.3 easymock easymock 1.0 mockobjects mockobjects-core 0.09 src/java src/test **/Test*.java **/Test*All.java maven-cactus-plugin
- 首先,定义你要运行时需要的jar文件。你不需要包含Cactus相关jar,因为Maven Cactus插件自动包含这些jar。
- Maven Cactus插件使用war文件。元素告诉Maven在生产的成品war中包括Commons BeanUtils jars。成品war已被Maven Cactus插件cactus化。注意因为你将会在你的servlet中使用Commons BeanUtils jars,所以必须包含对BeanUtils的依赖关系。
- 包含/排除纯粹的JUnit测试以匹配你想运行的测试。
- 列出生成的报告。
在运行Cactus插件之前,你要通告Cactus插件,Tomcat被安装在你机器哪个位置,这样才能在能在容器中运行Cactus测试。Cactus插件需要你在你的project.properties文件中增加下面的这一行:
cactus.home.tomcat4x =C:/Apps/jakarta-tomcat-4.1.24
完成Cactus servlet测试
使得测试通过的getCommand实现
package junitbook.servlets; import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AdminServlet extends HttpServlet { public static final String COMMAND_PARAM = "command"; public String getCommand(HttpServletRequest request) throws ServletException { String command = request.getParameter(COMMAND_PARAM); if (command == null) { throw new ServletException("Missing parameter [" + COMMAND_PARAM + "]"); } return command; } public void callView(HttpServletRequest request) { } }
测试callView方法
在TestAdminServlet中添加如下代码:
private Collection createCommandResult() throws Exception { List results = new ArrayList(); DynaProperty[] props = new DynaProperty[] { new DynaProperty("id", String.class), new DynaProperty("responsetime", Long.class) }; BasicDynaClass dynaClass = new BasicDynaClass("requesttime", null, props); DynaBean request1 = dynaClass.newInstance(); request1.set("id", "12345"); request1.set("responsetime", new Long(500)); results.add(request1); DynaBean request2 = dynaClass.newInstance(); request1.set("id", "56789"); request1.set("responsetime", new Long(430)); results.add(request2); return results; } public void testCallView() throws Exception { AdminServlet servlet = new AdminServlet(); // Set the result of the exection of the command in the // HTTP request so that the JSP page can get the data to // display request.setAttribute("result", createCommandResult()); servlet.callView(request); } public void beginDoGet(WebRequest request) { request.addParameter("command", "SELECT..."); }
public void callView(HttpServletRequest request) { } public Collection executeCommand(String command) throws Exception { throw new RuntimeException("not implemented"); }
public void beginDoGet(WebRequest request) { request.addParameter("command", "SELECT..."); } public void testDoGet() throws Exception { AdminServlet servlet = new AdminServlet() { public Collection executeCommand(String command) throws Exception { return createCommandResult(); } }; servlet.doGet(request, response); // Verify that the result of executing the command has been // stored in the HTTP request as an attribute that will be // passed to the JSP page. Collection results = (Collection) request.getAttribute("result"); assertNotNull("Failed to get execution results from the " + "request", results); assertEquals(2, results.size()); }
使得测试通过的doGet方法
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { Collection results = executeCommand(getCommand(request)); request.setAttribute("result", results); } catch (Exception e) { throw new ServletException( "Failed to execute command", e); } }
有两点需要注意,首先,对callView的调用在doGet中没有出现;测试也没有要求。其次executeCommand被调用,将抛出RuntimeException异常,当然你也可以返回null,但是抛出异常是个比较好的做法,如果方法被错误的调用,你也就不会感到任何的惊奇。