문제
서블릿의 단위 테스트를 수행하는 가장 좋은 방법이 무엇인지 알고 싶습니다.
내부 메소드를 테스트하는 것은 서블릿 컨텍스트를 참조하지 않는 한 문제가 되지 않습니다. 하지만 doGet/doPost 메소드뿐만 아니라 컨텍스트를 참조하거나 세션 매개변수를 사용하는 내부 메소드를 테스트하는 것은 어떻습니까?
JUnit이나 가급적이면 TestNG와 같은 기존 도구를 사용하여 이를 수행할 수 있는 방법이 있습니까?Tomcat 서버 같은 것을 내장해야 했나요?
해결책
노력하다 HttpUnit, 비록 단일 클래스의 '단위 테스트'보다는 모듈의 '통합 테스트'에 더 가까운 자동화된 테스트를 작성하게 될 가능성이 높습니다.
다른 팁
대부분의 경우 순수한 단위 테스트보다는 '통합 테스트'를 통해 서블릿과 JSP를 테스트합니다.다음을 포함하여 사용 가능한 JUnit/TestNG용 추가 기능이 많이 있습니다.
- HttpUnit (가장 오래되고 가장 잘 알려진 매우 낮은 수준으로 필요에 따라 좋거나 나쁠 수 있음)
- HtmlUnit (많은 프로젝트에 더 나은 HttpUnit보다 높은 수준)
- JWebUnit (다른 테스트 도구 위에 위치하여 이를 단순화하려고 시도합니다 - 제가 선호하는 도구입니다)
- 와티제이 및 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 플러그인을 사용하여 테스트를 수행하는 방법을 실제로 보여주는 더 완전한 솔루션을 게시해야겠다고 생각했습니다.
블로그에 전체 과정을 올렸어요 JUnit 4.x 및 HtmlUnit 2.x와 함께 GlassFish 3.1.1 Embedded 사용 Bitbucket에서 다운로드할 전체 프로젝트를 여기에 배치했습니다. 이미지 서블릿
이 질문을 보기 직전에 JSP/JSF 태그에 대한 이미지 서블릿의 다른 게시물을 보고 있었습니다.그래서 다른 게시물에서 사용한 솔루션을 이 게시물의 완전한 단위 테스트 버전과 결합했습니다.
테스트 방법
Apache Maven에는 다음을 포함하는 잘 정의된 수명 주기가 있습니다. test
.나는 이것을 다른 생명주기와 함께 사용할 것입니다. integration-test
내 솔루션을 구현합니다.
- Surefire 플러그인에서 표준 수명 주기 단위 테스트를 비활성화합니다.
- 추가하다
integration-test
확실한 플러그인 실행의 일부로 - GlassFish Maven 플러그인을 POM에 추가합니다.
- GlassFish가 실행되는 동안 실행되도록 구성합니다.
integration-test
수명주기. - 단위 테스트(통합 테스트)를 실행합니다.
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>
HTML단위
아래 예시와 같이 통합 테스트를 추가하세요.
@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();
}
블로그에 전체 과정을 올렸어요 JUnit 4.x 및 HtmlUnit 2.x와 함께 GlassFish 3.1.1 Embedded 사용 Bitbucket에서 다운로드할 전체 프로젝트를 여기에 배치했습니다. 이미지 서블릿
궁금한 점이 있으시면 댓글을 남겨주세요.나는 이것이 서블릿에 대해 계획하고 있는 모든 테스트의 기초로 사용할 수 있는 하나의 완전한 예라고 생각합니다.
단위 테스트에서 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
}
}
모의 생성 방법을 기본 서블릿 슈퍼클래스에 넣고 모든 서블릿 단위 테스트를 통해 이를 확장하는 것이 더 좋습니다.
모의러너(http://mockrunner.sourceforge.net/index.html) 할 수 있습니다.이는 서블릿을 테스트하는 데 사용할 수 있는 모의 J2EE 컨테이너를 제공합니다.또한 EJB, JDBC, JMS, Struts와 같은 다른 서버 측 코드를 단위 테스트하는 데 사용할 수도 있습니다.나는 JDBC와 EJB 기능만을 사용해왔습니다.
서블릿 doPost() 메소드에 대한 JUnit 테스트 구현은 인스턴스를 모형화하기 위해 Mockito 라이브러리에만 의존합니다. HttpRequest
, HttpResponse
, HttpSession
, ServletResponse
그리고 RequestDispatcher
.매개변수 키와 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 웹사이트.