用mock objects测试servlet
我们已经看过了用Cactus进行servlet的测试,接下来我们使用mock objects来做同样的练习。
在前面mock的学习中,我们使用了EasyMock编写mock objects。这次我们使用的是DynaMock API 来编写,DynaMock API是MockObjects.com中的一部分,他们都是用DynaMock Proxies在运行时间内生成mock objects。然而,在结构上,DynaMock比EasyMock更加的具有优势:它的API更加的全面(特别是在定义预期的方法里面),而且它生成的代码较简洁。缺点是它的使用稍微的复杂一点,并且是个不太成熟的框架。
EasyMock与DynaMock的比较
DynaMock | 更加简洁的代码(约是EasyMock的一半) |
EasyMock | 提供更加健壮的类型转换,对自动完成和接口改变时非常有用 |
DynaMock | 有一个更加全面的API |
EasyMock | 更加成熟 |
使用DynaMocks和DynaBeans写一个测试
下面的代码重新实现了上一笔记中描述的testGetCommandOK和testGetCommandNotDefined
package junitbook.servlets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import junit.framework.TestCase; import org.apache.commons.beanutils.BasicDynaClass; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaProperty; import com.mockobjects.dynamic.C; import com.mockobjects.dynamic.Mock; public class TestAdminServletMO extends TestCase { private Mock mockRequest; 建立新的mock private Mock mockResponse; private HttpServletRequest request; private HttpServletResponse response; private AdminServlet servlet; protected void setUp() { servlet = new AdminServlet() ; { ; public Collection executeCommand(String command) ; throws Exception ; { return createCommandResult(); } }; mockRequest = new Mock(HttpServletRequest.class); 你在代码中使用了一个HttpServletRequest对象来测试;之所以这样做是因为 request = (HttpServletRequest) mockRequest.proxy();你不是在一个容器内运行,你需要为它创建一个仿制品。 mockResponse = new Mock(HttpServletResponse.class); 设定mock的HttpServletRequest对象的行为,同时让DynaMock去查证 response = (HttpServletResponse) mockResponse.proxy(); 方法被调用而且传递的参数与所期望的匹配。 } protected void tearDown() ;让你的mock查证你设置的预期和你为之定义的行为的方法被调用了。 { mockRequest.verify(); } public void testGetCommandOk() throws Exception { mockRequest.expectAndReturn("getParameter", "command", ;当你带有”command”字符串作为参数的getParameter方法时, "SELECT..."); ;让mock返回“select…” String command = servlet.getCommand(request); assertEquals("SELECT...", command); } public void testGetCommandNotDefined() { mockRequest.expectAndReturn("getParameter", ;当调用了带有字符串getParameter方法时,让mock返回null C.isA(String.class), null); try { servlet.getCommand(request); fail("Command should not have existed"); } catch (ServletException expected) { assertTrue(true); } } 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 { servlet.callView(request); } public void testDoGet() throws Exception { mockRequest.expectAndReturn("getParameter", "command", "SELECT..."); // 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. mockRequest.expect("setAttribute", C.args(C.eq("result"), C.isA(Collection.class))); servlet.doGet(request, response); } }
用Cactus写filter测试
对SecurityFilter的要求是拦截所有的Http请求并且查证传入的SQL语句不包含任何的恶意指令。目前,你只要检查SQL查询中是否包含SELECT语句;如果不包含,将会转到一个错误页面。
SecurityFilter.java
package junitbook.servlets; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class SecurityFilter implements Filter { private String securityErrorPage; public void init(FilterConfig theConfig) throws ServletException { this.securityErrorPage = theConfig.getInitParameter("securityErrorPage"); 从web.xml获得错误页面的名字 } public void doFilter(ServletRequest theRequest, ServletResponse theResponse, FilterChain theChain) throws IOException, ServletException { String sqlCommand = theRequest.getParameter(AdminServlet.COMMAND_PARAM); if (!sqlCommand.startsWith("SELECT")) { // Forward to an error page RequestDispatcher dispatcher = theRequest.getRequestDispatcher( 转向错误页面 this.securityErrorPage); dispatcher.forward(theRequest, theResponse); } else { theChain.doFilter(theRequest, theResponse); } } public void destroy() { } }
用Cactus测试这个filter与你已经测试过的AdminServlet非常的相似。主要的差别在于TestCase继承是FilterTestCase而不是ServletTestCase。这种变化允许测试访问Filter API对象(FilterConfig,Request,Response和FilterChain)。
用SELECT查询测试Filter
测试的是当传来的SQL查询是SELECT查询时的doFilter方法。
import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.cactus.FilterTestCase; import org.apache.cactus.WebRequest; import org.apache.cactus.WebResponse; public class TestSecurityFilter extends FilterTestCase { public void beginDoFilterAllowedSQL(WebRequest request) { request.addParameter("command", "SELECT [...]"); } public void testDoFilterAllowedSQL() throws Exception { SecurityFilter filter = new SecurityFilter(); FilterChain mockFilterChain = new FilterChain() { public void doFilter(ServletRequest theRequest, ServletResponse theResponse) throws IOException, ServletException { } public void init(FilterConfig theConfig) { } public void destroy() { } }; filter.doFilter(request, response, mockFilterChain); } public void beginDoFilterForbiddenSQL(WebRequest request) { request.addParameter("command", "UPDATE [...]"); } public void testDoFilterForbiddenSQL() throws Exception { config.setInitParameter("securityErrorPage", "/securityError.jsp"); SecurityFilter filter = new SecurityFilter(); filter.init(config); filter.doFilter(request, response, filterChain); } public void endDoFilterForbiddenSQL(WebResponse response) { assertTrue("Bad response page", response.getText().indexOf( "<title>Security Error Page</title>") > 0); } }
对于这个测试,使用Cactus beginXXX方法把SQL指令加入你的filter处理的HTTP请求中,注意你的SQL查询是一个SELECT查询。让filter不要调
用链中的下一个filter(或jsp/servlet target),这样你就创建了一个FilterChain的空实现。你也可以让filter调用链中的一个元素,然而
一个filter与处理链中在该filter之后调用的filter/jsp/servlet target是完全独立的,这就是得单独测试filter更有意义,特别是考虑到
filter不会更改返回的http响应。
何时使用Cactus,何时使用mock objects
- 主要差异在于Cactus不仅能运行单元测试,而且能运行集成测试和某种程度上的功能测试,当然,更多的好处带来更多的复杂性。
- mock比较难写
- Cactus为那些你只需要设置的一些初始条件的情况提供实体对象
- 如果是对单元测试的应用程序已经写了,它通常必须是重复代理过的以支持mock objects测试。额外的重复代理对Cactus通常不需要。
一个好的策略就是要将商务逻辑代码从集成代码(与容器相互作用的代码)分离。然后
- 使用mock 测试商务代码
- 使用Cactus测试集成代码
小结:本章示范了如何对servlet和filter进行单元测试,在更广泛的意义上是对使用了Servlet/filter API的任何代码进行单元测试。
在下一章,我们将把注意力转换到JSP和Taglib API上。