Модульное тестирование сервлета Java

Я хотел бы знать, как лучше всего провести модульное тестирование сервлета.

Тестирование внутренних методов не является проблемой, если они не относятся к контексту сервлета, но как насчет тестирования методов doGet / doPost, а также внутреннего метода, которые ссылаются на контекст или используют параметры сеанса?

Есть ли способ сделать это просто с помощью классических инструментов, таких как JUnit или, желательно, TestNG? Мне нужно было встроить сервер Tomcat или что-то в этом роде?

возможный дубликат Сервлеты для модульного тестирования

Raedwald 10.06.2013 15:51
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
54
1
50 953
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

Попробуйте HttpUnit, хотя вы, скорее всего, в конечном итоге напишете автоматизированные тесты, которые являются скорее «интеграционными тестами» (модуля), чем «модульными тестами» (одного класса).

Что ж, это действительно больше о модульном тестировании, поскольку я бы, если возможно, заменил все взаимодействие между классом сервлета с другими классами взаимодействием с макетами.

gizmo 18.09.2008 12:25

HttpUnit, похоже, не претерпел изменений с 2008 года, что позволяет предположить, что это мертвый проект.

Raedwald 09.06.2013 22:30

Есть ли более новая замена HttpUnit?

oconnor0 11.06.2013 21:36

@Raedwald HttpUnit не мертв, см .: stackoverflow.com/questions/8630395/…

Guy 22.04.2015 09:29

Обнаружено, что HttpUnit (по крайней мере, пакет на Maven) не работает с сервлетами, которые используют getServletContext (). Выбрасывает java.lang.NoSuchMethodError.

Mike 23.10.2015 14:49

Вы вызываете методы 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   
   }
}

Еще лучше поместить методы создания макетов в базовый суперкласс сервлетов и сделать все модульные тесты сервлетов для его расширения.

HttpServletRequestWrapper не имеет конструктора по умолчанию, только один с параметром HttpServletRequest.

antony.trupe 18.12.2010 07:06

В большинстве случаев я тестирую сервлеты и JSP с помощью «интеграционных тестов», а не чистых модульных тестов. Доступно большое количество надстроек для JUnit / TestNG, включая:

  • HttpUnit (самый старый и самый известный, очень низкий уровень, который может быть хорошим или плохим в зависимости от ваших потребностей)
  • HtmlUnit (более высокий уровень, чем HttpUnit, что лучше для многих проектов)
  • JWebUnit (находится поверх других инструментов тестирования и пытается их упростить - тот, который я предпочитаю)
  • WatiJ и Selenium (используйте свой браузер для тестирования, что более тяжело, но реалистично)

Это тест 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;
}

Mockrunner (http://mockrunner.sourceforge.net/index.html) может это сделать. Он предоставляет фиктивный контейнер J2EE, который можно использовать для тестирования сервлетов. Его также можно использовать для модульного тестирования другого серверного кода, такого как EJB, JDBC, JMS, Struts. Я сам использовал только возможности JDBC и EJB.

mockrunner не обновлялся с 2009 года. Поддерживается ли какая-либо альтернатива?

datguy 10.10.2013 16:43

Я просмотрел опубликованные ответы и подумал, что опубликую более полное решение, которое на самом деле демонстрирует, как проводить тестирование с использованием встроенного GlassFish и его плагина Apache Maven.

Я написал полный процесс в моем блоге Использование GlassFish 3.1.1, встроенного с JUnit 4.x и HtmlUnit 2.x и разместил полный проект для загрузки на Bitbucket здесь: сервлет изображений

Я просматривал другой пост о сервлете изображений для тегов JSP / JSF незадолго до того, как увидел этот вопрос. Поэтому я объединил решение, которое использовал из другого поста, с полной тестовой версией для этого поста.

Как протестировать

Apache Maven имеет четко определенный жизненный цикл, включая test. Я буду использовать это вместе с другим жизненным циклом под названием integration-test для реализации своего решения.

  1. Отключите стандартное модульное тестирование жизненного цикла в плагине surefire.
  2. Добавьте integration-test как часть выполнения плагина surefire
  3. Добавьте плагин GlassFish Maven в POM.
  4. Настройте GlassFish для выполнения в течение жизненного цикла integration-test.
  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>

Плагин Surefire

Добавить / изменить плагин как часть <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();
}

Я написал полный процесс в моем блоге Использование GlassFish 3.1.1, встроенного с JUnit 4.x и HtmlUnit 2.x и разместил полный проект для загрузки на Bitbucket здесь: сервлет изображений

Если у вас есть вопросы, оставьте, пожалуйста, комментарий. Я думаю, что это один полный пример, который вы можете использовать в качестве основы для любого тестирования, которое вы планируете для сервлетов.

Обновлено в феврале 2018 г .: OpenBrace Limited закрылась и его продукт ObMimic больше не поддерживается.

Другое решение - использовать мою библиотеку ObMimic, которая специально разработана для модульного тестирования сервлетов. Он предоставляет полные реализации всех классов API сервлетов на простой Java, и вы можете настраивать и проверять их по мере необходимости для ваших тестов.

Вы действительно можете использовать его для прямого вызова методов doGet / doPost из тестов JUnit или TestNG, а также для тестирования любых внутренних методов, даже если они ссылаются на ServletContext или используют параметры сеанса (или любые другие функции Servlet API).

Это не требует внешнего или встроенного контейнера, не ограничивает вас более широкими «интеграционными» тестами на основе HTTP, и, в отличие от имитаций общего назначения, он имеет полное поведение Servlet API, «запеченное», так что ваши тесты могут быть: состояние "на основе, а не на основе взаимодействия" (например, ваши тесты не должны полагаться на точную последовательность вызовов Servlet API, сделанных вашим кодом, или на ваши собственные ожидания относительно того, как Servlet API будет реагировать на каждый вызов) .

В моем ответе на Как протестировать мой сервлет с помощью JUnit есть простой пример. Для получения полной информации и бесплатной загрузки посетите веб-сайт ObMimic.

«Чтобы установить ObMimic: распакуйте архив ObMimic-1.1.000.zip в то место, где ...» Подождите, что ?!

Innokenty 22.06.2015 16:42

Эта реализация теста 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);
    }
}

verify(session).setAttribute("field", "value") тоже, возможно, хорошее утверждение.

Mahdi 17.08.2016 23:43

В этом вопросе есть решение, предлагающее Mockito Как протестировать мой сервлет с помощью JUnit Это ограничивает задачу простым модульным тестированием без настройки какой-либо серверной среды.

Другие вопросы по теме