2008-09-18 55 views
51

我想知道对servlet进行单元测试的最佳方式是什么。单元测试Java Servlet

只要不引用servlet上下文,测试内部方法就不是问题,但是如何测试doGet/doPost方法以及引用上下文或使用会话参数的内部方法?

是否有一种方法可以简单地使用经典工具(如JUnit)或最佳TestNG?我是否需要嵌入一个tomcat服务器或类似的东西?

+0

[单位测试的servlet](可能重复http://stackoverflow.com/questions/53532/unit-testing- servlet) – Raedwald 2013-06-10 11:51:50

回答

12

尝试HttpUnit,尽管您可能最终编写的自动化测试比单元测试(单个类)更多的“集成测试”(模块)。

+0

嗯,它更像是单元测试,如果可能的话,通过与mock的交互来替换servlet类与其他类之间的所有交互。 – gizmo 2008-09-18 08:25:36

+2

自2008年以来,HttpUnit似乎没有任何变化,表明它是一个死的项目。 – Raedwald 2013-06-09 18:30:00

+3

是否有更新的HttpUnit替代品? – oconnor0 2013-06-11 17:36:52

6

您是否在单元测试中手动调用doPost和doGet方法?如果是这样,你可以重写HttpServletRequest方法来提供模拟对象。

myServlet.doGet(new HttpServletRequestWrapper() { 
    public HttpSession getSession() { 
     return mockSession; 
    } 

    ... 
} 

HttpServletRequestWrapper是一个方便的Java类。我建议你创建你的单元测试工具方法来创建模拟HTTP请求:

public void testSomething() { 
    myServlet.doGet(createMockRequest(), createMockResponse()); 
} 

protected HttpServletRequest createMockRequest() { 
    HttpServletRequest request = new HttpServletRequestWrapper() { 
     //overrided methods 
    } 
} 

它甚至更好,把一个基础的Servlet超模拟创建方法,使所有的servlet单元测试来扩展它。

43

大多数时候我通过“集成测试”而不是纯粹的单元测试来测试Servlets和JSP。有大量的附加组件的JUnit/TestNG的可用,包括:

  • HttpUnit(最古老和最有名的,非常低的水平,这可能是好是坏取决于你的需求)
  • HtmlUnit(高水平比HttpUnit的,这是很多项目更好)
  • JWebUnit(在其他测试工具上面坐着试图简化他们 - 一个我喜欢)
  • WatiJ和硒(使用浏览器做测试,更重量级但实际)

。这是一个简单的订单处理的Servlet一个JWebUnit的测试,其处理从所述表单输入“orderEntry.html”。它希望将客户ID,客户名称以及一个或多个订单项目:

public class OrdersPageTest { 
    private static final String WEBSITE_URL = "http://localhost:8080/demo1"; 

    @Before 
    public void start() { 
     webTester = new WebTester(); 
     webTester.setTestingEngineKey(TestingEngineRegistry.TESTING_ENGINE_HTMLUNIT); 
     webTester.getTestContext().setBaseUrl(WEBSITE_URL); 
    } 
    @Test 
    public void sanity() throws Exception { 
     webTester.beginAt("/orderEntry.html"); 
     webTester.assertTitleEquals("Order Entry Form"); 
    } 
    @Test 
    public void idIsRequired() throws Exception { 
     webTester.beginAt("/orderEntry.html"); 
     webTester.submit(); 
     webTester.assertTextPresent("ID Missing!"); 
    } 
    @Test 
    public void nameIsRequired() throws Exception { 
     webTester.beginAt("/orderEntry.html"); 
     webTester.setTextField("id","AB12"); 
     webTester.submit(); 
     webTester.assertTextPresent("Name Missing!"); 
    } 
    @Test 
    public void validOrderSucceeds() throws Exception { 
     webTester.beginAt("/orderEntry.html"); 
     webTester.setTextField("id","AB12"); 
     webTester.setTextField("name","Joe Bloggs"); 

     //fill in order line one 
     webTester.setTextField("lineOneItemNumber", "AA"); 
     webTester.setTextField("lineOneQuantity", "12"); 
     webTester.setTextField("lineOneUnitPrice", "3.4"); 

     //fill in order line two 
     webTester.setTextField("lineTwoItemNumber", "BB"); 
     webTester.setTextField("lineTwoQuantity", "14"); 
     webTester.setTextField("lineTwoUnitPrice", "5.6"); 

     webTester.submit(); 
     webTester.assertTextPresent("Total: 119.20"); 
    } 
    private WebTester webTester; 
} 
6

Mockrunner用(http://mockrunner.sourceforge.net/index.html)可以做到这一点。它提供了一个可用于测试Servlet的模拟J2EE容器。它也可以用于单元测试其他服务器端代码,如EJB,JDBC,JMS,Struts。我只使用了JDBC和EJB功能。

10

我看了看贴的答案,以为我会发布,实际上说明了如何使用嵌入式GlassFish和它的Apache Maven插件做的测试更加全面的解决方案。

我写的完整过程在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x并放置在整个项目上下载到位桶的位置:​​

我一直在寻找对JSP/JSF标签的图像Servlet中的另一个职位,我看到这个之前题。所以我将从另一篇文章中使用的解决方案与本文的完整单元测试版结合起来。

如何测试

的Apache Maven有一个明确定义的生命周期,包括test。我将使用这个名为integration-test的另一个生命周期来实现我的解决方案。

  1. 在surefire插件中禁用标准生命周期单元测试。
  2. integration-test作为surefire插件执行的一部分
  3. 将GlassFish Maven插件添加到POM。
  4. 将GlassFish配置为在integration-test生命周期中执行。
  5. 运行单元测试(集成测试)。

GlassFish的插件

添加这个插件为<build>的一部分。

 <plugin> 
      <groupId>org.glassfish</groupId> 
      <artifactId>maven-embedded-glassfish-plugin</artifactId> 
      <version>3.1.1</version> 
      <configuration> 
       <!-- This sets the path to use the war file we have built in the target directory --> 
       <app>target/${project.build.finalName}</app> 
       <port>8080</port> 
       <!-- This sets the context root, e.g. http://localhost:8080/test/ --> 
       <contextRoot>test</contextRoot> 
       <!-- This deletes the temporary files during GlassFish shutdown. --> 
       <autoDelete>true</autoDelete> 
      </configuration> 
      <executions> 
       <execution> 
        <id>start</id> 
        <!-- We implement the integration testing by setting up our GlassFish instance to start and deploy our application. --> 
        <phase>pre-integration-test</phase> 
        <goals> 
         <goal>start</goal> 
         <goal>deploy</goal> 
        </goals> 
       </execution> 
       <execution> 
        <id>stop</id> 
        <!-- After integration testing we undeploy the application and shutdown GlassFish gracefully. --> 
        <phase>post-integration-test</phase> 
        <goals> 
         <goal>undeploy</goal> 
         <goal>stop</goal> 
        </goals> 
       </execution> 
      </executions> 
     </plugin> 

保命插件

添加/修改插件为<build>的一部分。

 <plugin> 
      <groupId>org.apache.maven.plugins</groupId> 
      <artifactId>maven-surefire-plugin</artifactId> 
      <version>2.12.4</version> 
      <!-- We are skipping the default test lifecycle and will test later during integration-test --> 
      <configuration> 
       <skip>true</skip> 
      </configuration> 
      <executions> 
       <execution> 
        <phase>integration-test</phase> 
        <goals> 
         <!-- During the integration test we will execute surefire:test --> 
         <goal>test</goal> 
        </goals> 
        <configuration> 
         <!-- This enables the tests which were disabled previously. --> 
         <skip>false</skip> 
        </configuration> 
       </execution> 
      </executions> 
     </plugin> 

的HtmlUnit

添加集成测试,如下面的例子。

@Test 
public void badRequest() throws IOException { 
    webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); 
    webClient.getOptions().setPrintContentOnFailingStatusCode(false); 
    final HtmlPage page = webClient.getPage("http://localhost:8080/test/images/"); 
    final WebResponse response = page.getWebResponse(); 
    assertEquals(400, response.getStatusCode()); 
    assertEquals("An image name is required.", response.getStatusMessage()); 
    webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); 
    webClient.getOptions().setPrintContentOnFailingStatusCode(true); 
    webClient.closeAllWindows(); 
} 

我写的完整过程在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x并放置在整个项目上下载到位桶的位置:​​

如果您有任何疑问,请发表评论。我认为这是一个完整的例子,您可以使用它作为您计划使用servlet的任何测试的基础。

0

2018年2月更新:OpenBrace Limited has closed down,其ObMimic产品不再受支持。

另一种解决方案是使用我的ObMimic库,它专门为单元测试servlet而设计。它提供了所有Servlet API类的完整纯Java实现,您可以根据需要配置和检查这些实现。

确实可以使用它从JUnit或TestNG测试中直接调用doGet/doPost方法,即使它们引用ServletContext或使用会话参数(或任何其他Servlet API功能),也可以测试任何内部方法。

这不需要外部或嵌入式容器,不会限制您使用更广泛的基于HTTP的“集成”测试,而且与通用模拟不同,它具有完整的Servlet API行为“烘焙”,因此您的测试可以基于“基于状态”而不是“基于交互”(例如,您的测试不必依赖代码所做的Servlet API调用的精确顺序,也不需要根据您对Servlet API如何响应的期望到每个电话)。

在我对How to test my servlet using JUnit的回答中有一个简单的例子。有关完整详细信息和免费下载,请参阅ObMimic网站。

3

为的servlet的doPost JUnit测试的此实现()方法仅依赖于文库的Mockito为嘲笑起来的HttpRequestHttpResponseHttpSessionServletResponseRequestDispatcher实例。将参数键和JavaBean实例替换为与调用doPost()的关联JSP文件中引用的值相对应的实例。

的Maven的Mockito依赖性:

<dependency> 
     <groupId>org.mockito</groupId> 
     <artifactId>mockito-all</artifactId> 
     <version>1.9.5</version> 
</dependency> 

JUnit测试:

import javax.servlet.RequestDispatcher; 
import javax.servlet.ServletContext; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpSession; 

import java.io.IOException; 

import static org.junit.Assert.assertFalse; 
import static org.junit.Assert.assertTrue; 
import static org.mockito.Mockito.*; 

/** 
* Unit tests for the {@code StockSearchServlet} class. 
* @author Bob Basmaji 
*/ 
public class StockSearchServletTest extends HttpServlet { 
    // private fields of this class 
    private static HttpServletRequest request; 
    private static HttpServletResponse response; 
    private static StockSearchServlet servlet; 
    private static final String SYMBOL_PARAMETER_KEY = "symbol"; 
    private static final String STARTRANGE_PARAMETER_KEY = "startRange"; 
    private static final String ENDRANGE_PARAMETER_KEY = "endRange"; 
    private static final String INTERVAL_PARAMETER_KEY = "interval"; 
    private static final String SERVICETYPE_PARAMETER_KEY = "serviceType"; 

    /** 
    * Sets up the logic common to each test in this class 
    */ 
    @Before 
    public final void setUp() { 
     request = mock(HttpServletRequest.class); 
     response = mock(HttpServletResponse.class); 

     when(request.getParameter("symbol")) 
       .thenReturn("AAPL"); 

     when(request.getParameter("startRange")) 
       .thenReturn("2016-04-23 00:00:00"); 

     when(request.getParameter("endRange")) 
       .thenReturn("2016-07-23 00:00:00"); 

     when(request.getParameter("interval")) 
       .thenReturn("DAY"); 

     when(request.getParameter("serviceType")) 
       .thenReturn("WEB"); 

     String symbol = request.getParameter(SYMBOL_PARAMETER_KEY); 
     String startRange = request.getParameter(STARTRANGE_PARAMETER_KEY); 
     String endRange = request.getParameter(ENDRANGE_PARAMETER_KEY); 
     String interval = request.getParameter(INTERVAL_PARAMETER_KEY); 
     String serviceType = request.getParameter(SERVICETYPE_PARAMETER_KEY); 

     HttpSession session = mock(HttpSession.class); 
     when(request.getSession()).thenReturn(session); 
     final ServletContext servletContext = mock(ServletContext.class); 
     RequestDispatcher dispatcher = mock(RequestDispatcher.class); 
     when(servletContext.getRequestDispatcher("/stocksearchResults.jsp")).thenReturn(dispatcher); 
     servlet = new StockSearchServlet() { 
      public ServletContext getServletContext() { 
       return servletContext; // return the mock 
      } 
     }; 

     StockSearchBean search = new StockSearchBean(symbol, startRange, endRange, interval); 
     try { 
      switch (serviceType) { 
       case ("BASIC"): 
        search.processData(ServiceType.BASIC); 
        break; 
       case ("DATABASE"): 
        search.processData(ServiceType.DATABASE); 
        break; 
       case ("WEB"): 
        search.processData(ServiceType.WEB); 
        break; 
       default: 
        search.processData(ServiceType.WEB); 
      } 
     } catch (StockServiceException e) { 
      throw new RuntimeException(e.getMessage()); 
     } 
     session.setAttribute("search", search); 
    } 

    /** 
    * Verifies that the doPost method throws an exception when passed null arguments 
    * @throws ServletException 
    * @throws IOException 
    */ 
    @Test(expected = NullPointerException.class) 
    public final void testDoPostPositive() throws ServletException, IOException { 
     servlet.doPost(null, null); 
    } 

    /** 
    * Verifies that the doPost method runs without exception 
    * @throws ServletException 
    * @throws IOException 
    */ 
    @Test 
    public final void testDoPostNegative() throws ServletException, IOException { 
     boolean throwsException = false; 
     try { 
      servlet.doPost(request, response); 
     } catch (Exception e) { 
      throwsException = true; 
     } 
     assertFalse("doPost throws an exception", throwsException); 
    } 
}