Ответ 1
Попробуйте HttpUnit, хотя вы, вероятно, закончите писать автоматические тесты, которые являются более "интеграционными тестами" (модуля), чем "единичные тесты" (одного класса).
Я хотел бы знать, что было бы лучшим способом выполнить модульное тестирование сервлета.
Тестирование внутренних методов не является проблемой, если они не относятся к контексту сервлета, но как насчет тестирования методов doGet/doPost, а также внутреннего метода, который ссылается на контекст или использует параметры сеанса?
Есть ли способ сделать это, просто используя классические инструменты, такие как JUnit или, желательно, TestNG? Мне нужно встроить сервер tomcat или что-то в этом роде?
Попробуйте HttpUnit, хотя вы, вероятно, закончите писать автоматические тесты, которые являются более "интеграционными тестами" (модуля), чем "единичные тесты" (одного класса).
В большинстве случаев я тестирую сервлеты и JSP через "тесты интеграции", а не чистые тесты единиц. Существует большое количество надстроек для JUnit/TestNG, включая:
Это тест JWebUnit для простого сервлета обработки заказов, который обрабатывает ввод из формы "orderEntry.html". Он ожидает идентификатор клиента, имя клиента и один или несколько элементов заказа:
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.
Я написал полный процесс в своем блоге Использование GlassFish 3.1.1 Встраивается с JUnit 4.x и HtmlUnit 2.x и помещается полный проект для загрузки на Bitbucket здесь: image-servlet
Я смотрел еще одну запись на сервлете изображения для тегов JSP/JSF перед тем, как увидел этот вопрос. Поэтому я объединил решение, которое я использовал из другого сообщения, с полной версией, проверенной модулем для этого сообщения.
Apache Maven имеет четко определенный жизненный цикл, который включает test
. Я буду использовать это вместе с другим жизненным циклом под названием integration-test
для реализации моего решения.
integration-test
как часть выполнения верфи-плагинаintegration-test
жизненного цикла.Добавьте этот плагин как часть <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>
Добавьте интеграционные тесты, как показано ниже.
@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();
}
Я написал полный процесс в своем блоге Использование GlassFish 3.1.1 Встраивается с JUnit 4.x и HtmlUnit 2.x и помещается полный проект для загрузки на Bitbucket здесь: image-servlet
Если у вас есть какие-либо вопросы, оставьте комментарий. Я думаю, что это один полный пример использования вами в качестве основы для любого тестирования, которое вы планируете использовать для сервлетов.
Вы вызываете методы 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
}
}
Еще лучше включить методы создания макета в базовый сервлет-суперкласс и сделать все тестовые модули сервлетов для расширения.
Mockrunner (http://mockrunner.sourceforge.net/index.html) может это сделать. Он предоставляет макет J2EE-контейнера, который можно использовать для тестирования Servlets. Его также можно использовать для unit test другого серверного кода, такого как EJB, JDBC, JMS, Struts. Я использовал только возможности JDBC и EJB.
Эта реализация теста JUnit для метода doPost() сервлета используется только для библиотеки Mockito для издевательства экземпляров HttpRequest
, HttpResponse
, HttpSession
, ServletResponse
и RequestDispatcher
. Замените ключи параметров и экземпляр JavaBean теми, которые соответствуют значениям, указанным в соответствующем JSP файле, из которого вызывается doPost().
Зависимость 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);
}
}
Другим решением является использование библиотеки ObMimic, которая специально предназначена для модульного тестирования сервлетов. Он предоставляет полные простые Java-реализации всех классов Servlet API, и вы можете их настроить и проверить, если это необходимо для ваших тестов.
Вы действительно можете использовать его для прямого вызова методов doGet/doPost из тестов JUnit или TestNG и для проверки любых внутренних методов, даже если они ссылаются на ServletContext или используют параметры сеанса (или любые другие функции API сервлета).
Для этого не нужен внешний или встроенный контейнер, он не ограничивает вас более широкими "интеграционными" тестами на основе HTTP, и, в отличие от мошенников общего назначения, он имеет полное поведение API Servlet, "испеченное", поэтому ваши тесты может быть "государственным", а не "взаимодействующим" (например, ваши тесты не должны полагаться на точную последовательность вызовов API сервлета, сделанных вашим кодом, или на ваши собственные ожидания от того, как Servlet API будет реагировать на каждый вызов).
Вот простой пример в ответе Как проверить мой сервлет с помощью JUnit. Для получения полной информации и бесплатной загрузки см. Веб-сайт ObMimic.