IT이야기

자바 서블릿 단위 테스트

cyworld 2021. 10. 2. 17:52
반응형

자바 서블릿 단위 테스트


서블릿의 단위 테스트를 수행하는 가장 좋은 방법이 무엇인지 알고 싶습니다.

내부 메소드를 테스트하는 것은 서블릿 컨텍스트를 참조하지 않는 한 문제가 되지 않지만 doGet/doPost 메소드와 컨텍스트를 참조하거나 세션 매개변수를 사용하는 내부 메소드를 테스트하는 것은 어떻습니까?

JUnit 또는 가급적이면 TestNG와 같은 고전적인 도구를 사용하여 간단히 이 작업을 수행할 수 있는 방법이 있습니까? Tomcat 서버 또는 이와 유사한 것을 포함해야 했습니까?


(단일 클래스의) '단위 테스트'보다 더 많은 '통합 테스트'(모듈의)인 자동화된 테스트를 작성하게 될 가능성이 높지만 HttpUnit을 시도하십시오 .


대부분의 경우 순수한 단위 테스트가 아닌 '통합 테스트'를 통해 서블릿과 JSP를 테스트합니다. 다음을 포함하여 사용할 수 있는 JUnit/TestNG용 애드온이 많이 있습니다.

  • HttpUnit (가장 오래되고 가장 잘 알려진, 필요에 따라 좋거나 나쁠 수 있는 매우 낮은 수준)
  • HtmlUnit (많은 프로젝트에 더 나은 HttpUnit보다 높은 수준)
  • JWebUnit (다른 테스트 도구 위에 위치하며 이를 단순화하려고 합니다. 내가 선호하는 도구)
  • WatiJ 및 Selenium(브라우저를 사용하여 더 무겁지만 현실감 있는 테스트 수행)

이것은 'orderEntry.html' 형식의 입력을 처리하는 간단한 주문 처리 서블릿에 대한 JWebUnit 테스트입니다. 고객 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;
}

게시된 답변을 보고 임베디드 GlassFish와 Apache Maven 플러그인을 사용하여 테스트를 수행하는 방법을 실제로 보여주는 보다 완전한 솔루션을 게시할 것이라고 생각했습니다.

내 블로그 Using GlassFish 3.1.1 Embedded with JUnit 4.x 및 HtmlUnit 2.x 에 전체 프로세스를 작성하고 Bitbucket에서 다운로드할 전체 프로젝트를 여기: image-servlet에 배치했습니다.

이 질문을 보기 직전에 JSP/JSF 태그용 이미지 서블릿에 대한 다른 게시물을 보고 있었습니다. 그래서 다른 게시물에서 사용한 솔루션을 이 게시물의 완전한 단위 테스트 버전과 결합했습니다.

테스트 방법

Apache Maven에는 다음을 포함하는 잘 정의된 수명 주기가 있습니다 test. integration-test내 솔루션을 구현하기 위해 호출 되는 다른 수명 주기와 함께 이것을 사용할 것 입니다.

  1. Surefire 플러그인에서 표준 수명 주기 단위 테스트를 비활성화합니다.
  2. integration-testSurefire-plugin 실행의 일부로 추가
  3. POM에 GlassFish Maven 플러그인을 추가합니다.
  4. integration-test수명 주기 동안 실행되도록 GlassFish를 구성합니다 .
  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 및 HtmlUnit 2.x 에 전체 프로세스를 작성하고 Bitbucket에서 다운로드할 전체 프로젝트를 여기: image-servlet에 배치했습니다.

질문이 있으시면 댓글을 남겨주세요. 나는 이것이 서블릿에 대해 계획하고 있는 모든 테스트의 기초로 사용할 하나의 완전한 예라고 생각합니다.


단위 테스트에서 수동으로 doPost 및 doGet 메서드를 호출하고 있습니까? 그렇다면 HttpServletRequest 메소드를 재정의하여 모의 객체를 제공할 수 있습니다.

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

     ...
}

HttpServletRequestWrapper는 편의 자바 클래스입니다. 모의 http 요청을 만들기 위해 단위 테스트에서 유틸리티 메서드를 만드는 것이 좋습니다.

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

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

모의 생성 방법을 기본 서블릿 슈퍼클래스에 넣고 모든 서블릿 단위 테스트를 통해 이를 확장하는 것이 훨씬 좋습니다.


Mockrunner ( http://mockrunner.sourceforge.net/index.html )는 이것을 할 수 있습니다. 서블릿을 테스트하는 데 사용할 수 있는 모의 J2EE 컨테이너를 제공합니다. 또한 EJB, JDBC, JMS, Struts와 같은 다른 서버 측 코드를 단위 테스트하는 데 사용할 수도 있습니다. 저는 JDBC 및 EJB 기능만 사용했습니다.


서블릿의 doPost () 메소드에 대한 JUnit 테스트의이 구현의 인스턴스를 조롱하기위한에만 Mockito 라이브러리에 의존 HttpRequest, HttpResponse, HttpSession, ServletResponseRequestDispatcher. 매개변수 키 및 JavaBean 인스턴스를 doPost()가 호출되는 연관된 JSP 파일에서 참조되는 값에 해당하는 것으로 대체하십시오.

Mockito Maven 종속성:

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

2018년 2월 업데이트: OpenBrace Limited가 폐쇄 되었으며 ObMimic 제품이 더 이상 지원되지 않습니다.

또 다른 솔루션은 서블릿의 단위 테스트를 위해 특별히 설계된 ObMimic 라이브러리 를 사용 하는 것입니다. 모든 Servlet API 클래스의 완전한 일반 Java 구현을 제공하며 테스트에 필요한 대로 구성 및 검사할 수 있습니다.

JUnit 또는 TestNG 테스트에서 doGet/doPost 메소드를 직접 호출하고 ServletContext를 참조하거나 세션 매개변수(또는 다른 Servlet API 기능)를 사용하더라도 내부 메소드를 테스트하는 데 실제로 사용할 수 있습니다.

이것은 외부 또는 내장된 컨테이너가 필요하지 않으며 더 광범위한 HTTP 기반 "통합" 테스트로 제한하지 않으며 범용 모의와 달리 전체 Servlet API 동작을 "구축"하므로 테스트가 " "상호작용" 기반이 아닌 상태" 기반(예: 테스트에서 코드에 의해 수행된 Servlet API 호출의 정확한 순서에 의존하거나 Servlet API가 각 호출에 응답하는 방식에 대한 자신의 기대에 의존할 필요가 없음) .

JUnit을 사용하여 내 서블릿을 테스트하는 방법에 대한 내 대답에 간단한 예가 있습니다. 자세한 내용과 무료 다운로드는 ObMimic 웹사이트를 참조하십시오 .

참조URL : https://stackoverflow.com/questions/90907/unit-testing-a-java-servlet

반응형