[unit-testing] JUnit을 사용하여 서블릿을 테스트하는 방법

Java Servlet을 사용하여 웹 시스템을 만들었으며 이제 JUnit 테스트를 만들고 싶습니다. My dataManager는 데이터베이스에 제출하는 기본 코드입니다. JUnit으로 Servlet을 어떻게 테스트 하시겠습니까?

사용자가 등록 / 가입 할 수 있도록하는 내 코드 예제는 AJAX를 통해 내 메인 페이지에서 제출됩니다.

public void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException{

    // Get parameters
    String userName = request.getParameter("username");
    String password = request.getParameter("password");
    String name = request.getParameter("name");

    try {

        // Load the database driver
        Class.forName("com.mysql.jdbc.Driver");

        //pass reg details to datamanager       
        dataManager = new DataManager();
        //store result as string
        String result = dataManager.register(userName, password, name);

        //set response to html + no cache
        response.setContentType("text/html");
        response.setHeader("Cache-Control", "no-cache");
        //send response with register result
        response.getWriter().write(result);

    } catch(Exception e){
        System.out.println("Exception is :" + e);
    }
}



답변

Mockito 를 사용 하여 모의가 올바른 매개 변수를 반환하도록하고, 실제로 호출되었는지 확인하고 (선택적으로 횟수 지정) ‘결과’를 작성하고 올바른지 확인할 수 있습니다.

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.*;
import javax.servlet.http.*;
import org.apache.commons.io.FileUtils;
import org.junit.Test;

public class TestMyServlet extends Mockito{

    @Test
    public void testServlet() throws Exception {
        HttpServletRequest request = mock(HttpServletRequest.class);
        HttpServletResponse response = mock(HttpServletResponse.class);

        when(request.getParameter("username")).thenReturn("me");
        when(request.getParameter("password")).thenReturn("secret");

        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        when(response.getWriter()).thenReturn(writer);

        new MyServlet().doPost(request, response);

        verify(request, atLeast(1)).getParameter("username"); // only if you want to verify username was called...
        writer.flush(); // it may not have been flushed yet...
        assertTrue(stringWriter.toString().contains("My expected string"));
    }
}


답변

우선, 실제 애플리케이션에서는 서블릿에서 데이터베이스 연결 정보를 얻을 수 없습니다. 앱 서버에서 구성합니다.

그러나 컨테이너를 실행하지 않고 서블릿을 테스트하는 방법이 있습니다. 하나는 모의 객체를 사용하는 것입니다. Spring은 HttpServletRequest, HttpServletResponse, HttpServletSession 등과 같은 것들에 대해 매우 유용한 모의 세트를 제공합니다.

http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/mock/web/package-summary.html

이 모의를 사용하여 다음과 같은 것을 테스트 할 수 있습니다.

사용자 이름이 요청에 없으면 어떻게됩니까?

사용자 이름이 요청에 있으면 어떻게됩니까?

기타

그런 다음 다음과 같은 작업을 수행 할 수 있습니다.

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

public class MyServletTest {
    private MyServlet servlet;
    private MockHttpServletRequest request;
    private MockHttpServletResponse response;

    @Before
    public void setUp() {
        servlet = new MyServlet();
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();
    }

    @Test
    public void correctUsernameInRequest() throws ServletException, IOException {
        request.addParameter("username", "scott");
        request.addParameter("password", "tiger");

        servlet.doPost(request, response);

        assertEquals("text/html", response.getContentType());

        // ... etc
    }
}


답변

Selenium 테스트는 통합 또는 기능 (엔드 투 엔드) 테스트에 더 유용합니다. 나는 org.springframework.mock.web 을 사용하려고 노력하고 있지만 그리 멀지 않았습니다. jMock 으로 샘플 컨트롤러를 연결하고 있습니다. 테스트 스위트 .

첫째, 컨트롤러 :

package com.company.admin.web;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.company.admin.domain.PaymentDetail;
import com.company.admin.service.PaymentSearchService;
import com.company.admin.service.UserRequestAuditTrail;
import com.company.admin.web.form.SearchCriteria;

/**
 * Controls the interactions regarding to the refunds.
 *
 * @author slgelma
 *
 */
@Controller
@SessionAttributes({"user", "authorization"})
public class SearchTransactionController {

    public static final String SEARCH_TRANSACTION_PAGE = "searchtransaction";

    private PaymentSearchService searchService;
    //private Validator searchCriteriaValidator;
    private UserRequestAuditTrail notifications;

    @Autowired
    public void setSearchService(PaymentSearchService searchService) {
        this.searchService = searchService;
    }

    @Autowired
    public void setNotifications(UserRequestAuditTrail notifications) {
        this.notifications = notifications;
    }

    @RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE)
    public String setUpTransactionSearch(Model model) {
        SearchCriteria searchCriteria = new SearchCriteria();
        model.addAttribute("searchCriteria", searchCriteria);
        notifications.transferTo(SEARCH_TRANSACTION_PAGE);
        return SEARCH_TRANSACTION_PAGE;
    }

    @RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="cancel")
    public String cancelSearch() {
        notifications.redirectTo(HomeController.HOME_PAGE);
        return "redirect:/" + HomeController.HOME_PAGE;
    }

    @RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="execute")
    public String executeSearch(
            @ModelAttribute("searchCriteria") @Valid SearchCriteria searchCriteria,
            BindingResult result, Model model,
            SessionStatus status) {
        //searchCriteriaValidator.validate(criteria, result);
        if (result.hasErrors()) {
            notifications.transferTo(SEARCH_TRANSACTION_PAGE);
            return SEARCH_TRANSACTION_PAGE;
        } else {
            PaymentDetail payment =
                searchService.getAuthorizationFor(searchCriteria.geteWiseTransactionId());
            if (payment == null) {
                ObjectError error = new ObjectError(
                        "eWiseTransactionId", "Transaction not found");
                result.addError(error);
                model.addAttribute("searchCriteria", searchCriteria);
                notifications.transferTo(SEARCH_TRANSACTION_PAGE);
                return SEARCH_TRANSACTION_PAGE;
            } else {
                model.addAttribute("authorization", payment);
                notifications.redirectTo(PaymentDetailController.PAYMENT_DETAIL_PAGE);
                return "redirect:/" + PaymentDetailController.PAYMENT_DETAIL_PAGE;
            }
        }
    }

}

다음으로 테스트 :

    package test.unit.com.company.admin.web;

    import static org.hamcrest.Matchers.containsString;
    import static org.hamcrest.Matchers.equalTo;
    import static org.junit.Assert.assertThat;

    import org.jmock.Expectations;
    import org.jmock.Mockery;
    import org.jmock.integration.junit4.JMock;
    import org.jmock.integration.junit4.JUnit4Mockery;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.ObjectError;
    import org.springframework.web.bind.support.SessionStatus;

    import com.company.admin.domain.PaymentDetail;
    import com.company.admin.service.PaymentSearchService;
    import com.company.admin.service.UserRequestAuditTrail;
    import com.company.admin.web.HomeController;
    import com.company.admin.web.PaymentDetailController;
    import com.company.admin.web.SearchTransactionController;
    import com.company.admin.web.form.SearchCriteria;

    /**
     * Tests the behavior of the SearchTransactionController.
     * @author slgelma
     *
     */
    @RunWith(JMock.class)
    public class SearchTransactionControllerTest {

        private final Mockery context = new JUnit4Mockery();
        private final SearchTransactionController controller = new SearchTransactionController();
        private final PaymentSearchService searchService = context.mock(PaymentSearchService.class);
        private final UserRequestAuditTrail notifications = context.mock(UserRequestAuditTrail.class);
        private final Model model = context.mock(Model.class);


        /**
         * @throws java.lang.Exception
         */
        @Before
        public void setUp() throws Exception {
            controller.setSearchService(searchService);
            controller.setNotifications(notifications);
        }

        @Test
        public void setUpTheSearchForm() {

            final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;

            context.checking(new Expectations() {{
                oneOf(model).addAttribute(
                        with(any(String.class)), with(any(Object.class)));
                oneOf(notifications).transferTo(with(any(String.class)));
            }});

            String nextPage = controller.setUpTransactionSearch(model);
            assertThat("Controller is not requesting the correct form",
                    target, equalTo(nextPage));
        }

        @Test
        public void cancelSearchTest() {

            final String target = HomeController.HOME_PAGE;

            context.checking(new Expectations(){{
                never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
                oneOf(notifications).redirectTo(with(any(String.class)));
            }});

            String nextPage = controller.cancelSearch();
            assertThat("Controller is not requesting the correct form",
                    nextPage, containsString(target));
        }

        @Test
        public void executeSearchWithNullTransaction() {

            final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;

            final SearchCriteria searchCriteria = new SearchCriteria();
            searchCriteria.seteWiseTransactionId(null);

            final BindingResult result = context.mock(BindingResult.class);
            final SessionStatus status = context.mock(SessionStatus.class);

            context.checking(new Expectations() {{
                allowing(result).hasErrors(); will(returnValue(true));
                never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
                never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
                oneOf(notifications).transferTo(with(any(String.class)));
            }});

            String nextPage = controller.executeSearch(searchCriteria, result, model, status);
            assertThat("Controller is not requesting the correct form",
                    target, equalTo(nextPage));
        }

        @Test
        public void executeSearchWithEmptyTransaction() {

            final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;

            final SearchCriteria searchCriteria = new SearchCriteria();
            searchCriteria.seteWiseTransactionId("");

            final BindingResult result = context.mock(BindingResult.class);
            final SessionStatus status = context.mock(SessionStatus.class);

            context.checking(new Expectations() {{
                allowing(result).hasErrors(); will(returnValue(true));
                never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
                never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
                oneOf(notifications).transferTo(with(any(String.class)));
            }});

            String nextPage = controller.executeSearch(searchCriteria, result, model, status);
            assertThat("Controller is not requesting the correct form",
                    target, equalTo(nextPage));
        }

        @Test
        public void executeSearchWithTransactionNotFound() {

            final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
            final String badTransactionId = "badboy";
            final PaymentDetail transactionNotFound = null;

            final SearchCriteria searchCriteria = new SearchCriteria();
            searchCriteria.seteWiseTransactionId(badTransactionId);

            final BindingResult result = context.mock(BindingResult.class);
            final SessionStatus status = context.mock(SessionStatus.class);

            context.checking(new Expectations() {{
                allowing(result).hasErrors(); will(returnValue(false));
                atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
                oneOf(searchService).getAuthorizationFor(with(any(String.class)));
                    will(returnValue(transactionNotFound));
                oneOf(result).addError(with(any(ObjectError.class)));
                oneOf(notifications).transferTo(with(any(String.class)));
            }});

            String nextPage = controller.executeSearch(searchCriteria, result, model, status);
            assertThat("Controller is not requesting the correct form",
                    target, equalTo(nextPage));
        }

        @Test
        public void executeSearchWithTransactionFound() {

            final String target = PaymentDetailController.PAYMENT_DETAIL_PAGE;
            final String goodTransactionId = "100000010";
            final PaymentDetail transactionFound = context.mock(PaymentDetail.class);

            final SearchCriteria searchCriteria = new SearchCriteria();
            searchCriteria.seteWiseTransactionId(goodTransactionId);

            final BindingResult result = context.mock(BindingResult.class);
            final SessionStatus status = context.mock(SessionStatus.class);

            context.checking(new Expectations() {{
                allowing(result).hasErrors(); will(returnValue(false));
                atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
                oneOf(searchService).getAuthorizationFor(with(any(String.class)));
                    will(returnValue(transactionFound));
                oneOf(notifications).redirectTo(with(any(String.class)));
            }});

            String nextPage = controller.executeSearch(searchCriteria, result, model, status);
            assertThat("Controller is not requesting the correct form",
                    nextPage, containsString(target));
        }

    }

도움이 되었기를 바랍니다.


답변

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

OpenBrace의 Servlet API test-doubles 의 ObMimic 라이브러리를 사용하는 또 다른 대안이 있습니다 (공개 : 저는 개발자입니다).

package com.openbrace.experiments.examplecode.stackoverflow5434419;

import static org.junit.Assert.*;
import com.openbrace.experiments.examplecode.stackoverflow5434419.YourServlet;
import com.openbrace.obmimic.mimic.servlet.ServletConfigMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
import com.openbrace.obmimic.substate.servlet.RequestParameters;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Example tests for {@link YourServlet#doPost(HttpServletRequest,
 * HttpServletResponse)}.
 *
 * @author Mike Kaufman, OpenBrace Limited
 */
public class YourServletTest {

    /** The servlet to be tested by this instance's test. */
    private YourServlet servlet;

    /** The "mimic" request to be used in this instance's test. */
    private HttpServletRequestMimic request;

    /** The "mimic" response to be used in this instance's test. */
    private HttpServletResponseMimic response;

    /**
     * Create an initialized servlet and a request and response for this
     * instance's test.
     *
     * @throws ServletException if the servlet's init method throws such an
     *     exception.
     */
    @Before
    public void setUp() throws ServletException {
        /*
         * Note that for the simple servlet and tests involved:
         * - We don't need anything particular in the servlet's ServletConfig.
         * - The ServletContext isn't relevant, so ObMimic can be left to use
         *   its default ServletContext for everything.
         */
        servlet = new YourServlet();
        servlet.init(new ServletConfigMimic());
        request = new HttpServletRequestMimic();
        response = new HttpServletResponseMimic();
    }

    /**
     * Test the doPost method with example argument values.
     *
     * @throws ServletException if the servlet throws such an exception.
     * @throws IOException if the servlet throws such an exception.
     */
    @Test
    public void testYourServletDoPostWithExampleArguments()
            throws ServletException, IOException {

        // Configure the request. In this case, all we need are the three
        // request parameters.
        RequestParameters parameters
            = request.getMimicState().getRequestParameters();
        parameters.set("username", "mike");
        parameters.set("password", "xyz#zyx");
        parameters.set("name", "Mike");

        // Run the "doPost".
        servlet.doPost(request, response);

        // Check the response's Content-Type, Cache-Control header and
        // body content.
        assertEquals("text/html; charset=ISO-8859-1",
            response.getMimicState().getContentType());
        assertArrayEquals(new String[] { "no-cache" },
            response.getMimicState().getHeaders().getValues("Cache-Control"));
        assertEquals("...expected result from dataManager.register...",
            response.getMimicState().getBodyContentAsString());

    }

}

노트:

  • 각 “mimic”에는 논리적 상태에 대한 “mimicState”개체가 있습니다. 이는 Servlet API 메소드와 모방 체의 내부 상태에 대한 구성 및 검사를 명확하게 구분합니다.

  • Content-Type 검사에 “charset = ISO-8859-1″이 포함되어 있다는 사실에 놀랄 것입니다. 그러나 주어진 “doPost”코드의 경우 이는 Servlet API Javadoc 및 HttpServletResponse의 자체 getContentType 메소드, 예를 들어 Glassfish 3에서 생성 된 실제 Content-Type 헤더에 따른 것입니다. 일반 모의 객체를 사용하고 API의 동작에 대한 자체 기대치입니다. 이 경우에는 문제가되지 않지만 더 복잡한 경우에는 약간의 조롱 거리를 만들 수있는 예상치 못한 API 동작입니다!

  • response.getMimicState().getContentType()Content-Type을 확인하고 위의 요점을 설명하는 가장 간단한 방법으로 사용 했지만 원하는 경우 (를 사용하여 response.getMimicState().getContentTypeMimeType()) “text / html”자체를 확인할 수 있습니다 . Cache-Control 헤더와 동일한 방식으로 Content-Type 헤더를 확인하는 것도 작동합니다.

  • 이 예에서 응답 내용은 문자 데이터로 확인됩니다 (작성기의 인코딩 사용). 또한 응답의 Writer가 OutputStream (사용 response.getMimicState().isWritingCharacterContent()) 대신 사용되었는지 확인할 수 있지만 결과 출력에만 관심이 있고 API 호출이 생성 한 내용은 신경 쓰지 않습니다. 너무 확인 …). 응답의 본문 내용을 바이트로 검색하고 Writer / OutputStream의 자세한 상태 등을 검사 할 수도 있습니다.

ObMimic에 대한 자세한 내용은 OpenBrace 웹 사이트 에서 무료로 다운로드 할 수 있습니다. 또는 질문이있는 경우 저에게 연락 할 수 있습니다 (연락처 정보는 웹 사이트에 있음).


답변

편집 : 선인장은 이제 죽은 프로젝트입니다 : http://attic.apache.org/projects/jakarta-cactus.html


선인장을보고 싶을 수도 있습니다.

http://jakarta.apache.org/cactus/

프로젝트 설명

Cactus는 서버 측 자바 코드 (서블릿, EJB, 태그 라이브러리, 필터 등)를 단위 테스트하기위한 간단한 테스트 프레임 워크입니다.

Cactus의 목적은 서버 측 코드에 대한 테스트 작성 비용을 낮추는 것입니다. JUnit을 사용하고 확장합니다.

Cactus는 컨테이너 내에서 테스트가 실행되는 컨테이너 내 전략을 구현합니다.


답변

또 다른 접근 방식은 서블릿을 “호스팅”하는 임베디드 서버를 생성하여 실제 서버를 호출하기위한 라이브러리를 사용하여 호출을 작성할 수 있도록하는 것입니다 (이 접근 방식의 유용성은 “합법적 인”프로그래밍 방식을 얼마나 쉽게 만들 수 있는지에 따라 달라집니다. 서버 호출-클라이언트가 많은 JMS (Java Messaging Service) 액세스 포인트를 테스트했습니다.)

갈 수있는 경로는 몇 가지가 있습니다. 보통 두 곳은 바람둥이와 부두입니다.

경고 : 포함 할 서버를 선택할 때주의해야 할 사항은 사용중인 servlet-api의 버전입니다 (HttpServletRequest와 같은 클래스를 제공하는 라이브러리). 2.5를 사용하는 경우 Jetty 6.x가 잘 작동하는 것을 발견했습니다 (아래에서 제공 할 예). servlet-api 3.0을 사용하는 경우 tomcat-7 임베디드 항목이 좋은 옵션 인 것처럼 보이지만 테스트중인 응용 프로그램이 servlet-api 2.5를 사용했기 때문에 사용하려는 시도를 포기해야했습니다. 두 가지를 혼합하려고하면 서버를 구성하거나 시작하려고 할 때 NoSuchMethod 및 기타 예외가 발생합니다.

다음과 같은 서버를 설정할 수 있습니다 (Jetty 6.1.26, servlet-api 2.5) :

public void startServer(int port, Servlet yourServletInstance){
    Server server = new Server(port);
    Context root = new Context(server, "/", Context.SESSIONS);

    root.addServlet(new ServletHolder(yourServletInstance), "/servlet/context/path");

    //If you need the servlet context for anything, such as spring wiring, you coudl get it like this
    //ServletContext servletContext = root.getServletContext();

    server.start();
}


답변

웹 기반 단위 테스트에 Selenium 을 사용하십시오 . 웹 페이지에서 작업을 기록하고 Selenium RC 를 사용 하여 테스트 서버를 실행하는 JUnit 테스트 케이스로 내보낼 수있는 Selenium IDE 라는 Firefox 플러그인 이 있습니다 .