对 Java Servlet 进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/90907/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 08:11:11  来源:igfitidea点击:

Unit testing a Java Servlet

javaunit-testingservlets

提问by gizmo

I would like to know what would be the best way to do unit testing of a servlet.

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

Testing internal methods is not a problem as long as they don't refer to the servlet context, but what about testing the doGet/doPost methods as well as the internal method that refer to the context or make use of session parameters?

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

Is there a way to do this simply using classical tools such as JUnit, or preferrably TestNG? Did I need to embed a tomcat server or something like that?

有没有办法简单地使用经典工具(如 JUnit 或最好是 TestNG)来做到这一点?我是否需要嵌入一个 tomcat 服务器或类似的东西?

采纳答案by Peter Hilton

Try HttpUnit, although you are likely to end up writing automated tests that are more 'integration tests' (of a module) than 'unit tests' (of a single class).

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

回答by Marcio Aguiar

Are you calling the doPost and doGet methods manually in the unit tests? If so you can override the HttpServletRequest methods to provide mock objects.

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

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

     ...
}

The HttpServletRequestWrapperis a convenience Java class. I suggest you to create a utility method in your unit tests to create the mock http requests:

了HttpServletRequestWrapper是一个方便的Java类。我建议您在单元测试中创建一个实用方法来创建模拟 http 请求:

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

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

It's even better to put the mock creation methods in a base servlet superclass and make all servlets unit tests to extend it.

将模拟创建方法放在基本 servlet 超类中并进行所有 servlet 单元测试以扩展它会更好。

回答by Garth Gilmour

Most of the time I test Servlets and JSP's via 'Integration Tests' rather than pure Unit Tests. There are a large number of add-ons for JUnit/TestNG available including:

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

  • HttpUnit(the oldest and best known, very low level which can be good or bad depending on your needs)
  • HtmlUnit(higher level than HttpUnit, which is better for many projects)
  • JWebUnit(sits on top of other testing tools and tries to simplify them - the one I prefer)
  • WatiJand Selenium (use your browser to do the testing, which is more heavyweight but realistic)
  • HttpUnit(最古老和最著名的,非常低的级别,根据您的需要可以是好是坏)
  • HtmlUnit(比HttpUnit更高一级,对很多项目来说更好)
  • JWebUnit(位于其他测试工具之上并试图简化它们——我更喜欢的那个)
  • WatiJ和 Selenium(用浏览器做测试,比较重量级但是比较真实)

This is a JWebUnit test for a simple Order Processing Servlet which processes input from the form 'orderEntry.html'. It expects a customer id, a customer name and one or more order items:

这是一个简单的订单处理 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;
}

回答by Kris Pruden

Mockrunner (http://mockrunner.sourceforge.net/index.html) can do this. It provides a mock J2EE container that can be used to test Servlets. It can also be used to unit test other server-side code like EJBs, JDBC, JMS, Struts. I've only used the JDBC and EJB capabilities myself.

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

回答by John Yeary

I looked at the posted answers and thought that I would post a more complete solution that actually demonstrates how to do the testing using embedded GlassFish and its Apache Maven plugin.

我查看了发布的答案,并认为我会发布一个更完整的解决方案,实际演示如何使用嵌入式 GlassFish 及其 Apache Maven 插件进行测试。

I wrote the complete process up on my blog Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.xand placed the complete project for download on Bitbucket here: image-servlet

我在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x上写了完整的过程,并将完整的项目放在 Bitbucket 上供下载:image-servlet

I was looking at another post on an image servlet for JSP/JSF tags just before I saw this question. So I combined the solution I used from the other post with a complete unit tested version for this post.

在我看到这个问题之前,我正在查看有关 JSP/JSF 标签的图像 servlet 的另一篇文章。所以我将我在另一篇文章中使用的解决方案与这篇文章的完整单元测试版本结合起来。

How to Test

如何测试

Apache Maven has a well defined lifecycle that includes test. I will use this along with another lifecycle called integration-testto implement my solution.

Apache Maven 具有明确定义的生命周期,其中包括test. 我将把它与另一个生命周期一起使用integration-test来实现我的解决方案。

  1. Disable standard lifecycle unit testing in the surefire plugin.
  2. Add integration-testas part of the executions of the surefire-plugin
  3. Add the GlassFish Maven plugin to the POM.
  4. Configure GlassFish to execute during the integration-testlifecycle.
  5. Run unit tests (integration tests).
  1. 在surefire插件中禁用标准生命周期单元测试。
  2. 添加integration-test作为surefire-plugin执行的一部分
  3. 将 GlassFish Maven 插件添加到 POM。
  4. 配置 GlassFish 以在integration-test生命周期内执行。
  5. 运行单元测试(集成测试)。

GlassFish Plugin

GlassFish 插件

Add this plugin as part of the <build>.

添加此插件作为<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>

Surefire Plugin

万无一失的插件

Add/modify the plugin as part of the <build>.

添加/修改插件作为<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

HTML单元

Add integration tests like the example below.

添加如下示例的集成测试。

@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();
}

I wrote the complete process up on my blog Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.xand placed the complete project for download on Bitbucket here: image-servlet

我在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x上写了完整的过程,并将完整的项目放在 Bitbucket 上供下载:image-servlet

If you have any questions, please leave a comment. I think that this is one complete example for you to use as the basis of any testing you are planning for servlets.

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

回答by Mike Kaufman

Updated Feb 2018: OpenBrace Limited has closed down, and its ObMimic product is no longer supported.

2018 年 2 月更新:OpenBrace Limited 已关闭,不再支持其 ObMimic 产品。

Another solution is to use my ObMimiclibrary, which is specifically designed for unit testing of servlets. It provides complete plain-Java implementations of all the Servlet API classes, and you can configure and inspect these as necessary for your tests.

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

You can indeed use it to directly call doGet/doPost methods from JUnit or TestNG tests, and to test any internal methods even if they refer to the ServletContext or use session parameters (or any other Servlet API features).

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

This doesn't need an external or embedded container, doesn't limit you to broader HTTP-based "integration" tests, and unlike general-purpose mocks it has the full Servlet API behaviour "baked in", so your tests can be "state"-based rather than "interaction"-based (e.g. your tests don't have to rely on the precise sequence of Servlet API calls made by your code, nor on your own expectations of how the Servlet API will respond to each call).

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

There's a simple example in my answer to How to test my servlet using JUnit. For full details and a free download see the ObMimicwebsite.

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

回答by Robert John

This implementation of a JUnit test for servlet doPost() method relies only on the Mockito library for mocking up instances of HttpRequest, HttpResponse, HttpSession, ServletResponseand RequestDispatcher. Replace parameter keys and JavaBean instance with those that correspond to values referenced in the associated JSP file from which doPost() is called.

对servlet的doPost()方法JUnit测试的这个实现只依赖于图书馆的Mockito的嘲讽起来的情况下HttpRequestHttpResponseHttpSessionServletResponseRequestDispatcher。将参数键和 JavaBean 实例替换为与调用 doPost() 的关联 JSP 文件中引用的值相对应的那些。

Mockito Maven dependency:

Mockito Maven 依赖项:

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

JUnit test:

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);
    }
}