web-dev-qa-db-fra.com

Test unitaire d'un Java servlet

Je voudrais savoir quelle serait la meilleure façon de faire des tests unitaires d'un servlet.

Le test des méthodes internes n'est pas un problème tant qu'elles ne font pas référence au contexte de servlet, mais qu'en est-il du test des méthodes doGet/doPost ainsi que de la méthode interne qui font référence au contexte ou utilisent des paramètres de session?

Existe-t-il un moyen de le faire simplement en utilisant des outils classiques tels que JUnit, ou de préférence TestNG? Dois-je intégrer un serveur Tomcat ou quelque chose comme ça?

52
gizmo

Essayez HttpUnit , bien que vous finissiez probablement par écrire des tests automatisés qui sont plus des "tests d'intégration" (d'un module) que des "tests unitaires" (d'une seule classe).

12
Peter Hilton

La plupart du temps, je teste les servlets et les JSP via des "tests d'intégration" plutôt que de purs tests unitaires. Il existe un grand nombre de modules complémentaires pour JUnit/TestNG, notamment:

  • HttpUnit (le niveau le plus ancien et le plus connu, très bas qui peut être bon ou mauvais selon vos besoins)
  • HtmlUnit (niveau supérieur à HttpUnit, ce qui est mieux pour de nombreux projets)
  • JWebUnit (se place au-dessus d'autres outils de test et essaie de les simplifier - celui que je préfère)
  • WatiJ et Selenium (utilisez votre navigateur pour faire le test, qui est plus lourd mais réaliste)

Il s'agit d'un test JWebUnit pour un simple servlet de traitement des commandes qui traite les entrées du formulaire "orderEntry.html". Il attend un identifiant client, un nom de client et un ou plusieurs articles de commande:

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;
}
44
Garth Gilmour

J'ai regardé les réponses publiées et pensé que je publierais une solution plus complète qui montre comment faire les tests en utilisant GlassFish intégré et son plugin Apache Maven.

J'ai écrit le processus complet sur mon blog tilisation de GlassFish 3.1.1 Embedded avec JUnit 4.x et HtmlUnit 2.x et j'ai placé le projet complet à télécharger sur Bitbucket ici: image-servlet

Je regardais un autre article sur une servlet d'image pour les balises JSP/JSF juste avant de voir cette question. J'ai donc combiné la solution que j'ai utilisée dans l'autre article avec une version complète testée unitaire pour cet article.

Comment tester

Apache Maven a un cycle de vie bien défini qui inclut test. Je vais l'utiliser avec un autre cycle de vie appelé integration-test pour implémenter ma solution.

  1. Désactivez les tests unitaires de cycle de vie standard dans le plugin surefire.
  2. Ajouter integration-test dans le cadre des exécutions du plugin surefire
  3. Ajoutez le plugin GlassFish Maven au POM.
  4. Configurez GlassFish pour qu'il s'exécute pendant le integration-test cycle de la vie.
  5. Exécutez des tests unitaires (tests d'intégration).

Plugin GlassFish

Ajoutez ce plugin dans le cadre du <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>

Plugin Surefire

Ajouter/modifier le plugin dans le cadre du <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

Ajoutez des tests d'intégration comme dans l'exemple ci-dessous.

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

J'ai écrit le processus complet sur mon blog tilisation de GlassFish 3.1.1 Embedded avec JUnit 4.x et HtmlUnit 2.x et j'ai placé le projet complet à télécharger sur Bitbucket ici: image-servlet

Si vous avez des questions, veuillez laisser un commentaire. Je pense que c'est un exemple complet que vous pouvez utiliser comme base de tout test que vous prévoyez pour les servlets.

10
John Yeary

Mockrunner ( http://mockrunner.sourceforge.net/index.html ) peut le faire. Il fournit un faux conteneur J2EE qui peut être utilisé pour tester les servlets. Il peut également être utilisé pour tester un autre code côté serveur comme les EJB, JDBC, JMS, Struts. Je n'ai utilisé que les capacités JDBC et EJB moi-même.

6
Kris Pruden

Appelez-vous les méthodes doPost et doGet manuellement dans les tests unitaires? Si tel est le cas, vous pouvez remplacer les méthodes HttpServletRequest pour fournir des objets fictifs.

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

     ...
}

La HttpServletRequestWrapper est une commodité Java class. Je vous suggère de créer une méthode utilitaire dans vos tests unitaires pour créer les fausses requêtes http:

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

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

Il est encore mieux de placer les méthodes de création de simulation dans une superclasse de servlet de base et de faire tous les tests unitaires des servlets pour l'étendre.

6
Marcio Aguiar

Cette implémentation d'un test JUnit pour la méthode doPost () de la servlet repose uniquement sur la bibliothèque Mockito pour la simulation d'instances de HttpRequest, HttpResponse, HttpSession, ServletResponse et RequestDispatcher. Remplacez les clés de paramètres et l'instance JavaBean par celles qui correspondent aux valeurs référencées dans le fichier JSP associé à partir duquel doPost () est appelé.

Dépendance Mockito Maven:

<dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-all</artifactId>
      <version>1.9.5</version>
</dependency>

Test 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);
    }
}
3
Bob Basmaji

Mise à jour février 2018: OpenBrace Limited a fermé , et son produit ObMimic n'est plus pris en charge.

Une autre solution consiste à utiliser ma bibliothèque ObMimic , spécialement conçue pour les tests unitaires des servlets. Il fournit des implémentations complètes en Java simple de toutes les classes d'API Servlet, et vous pouvez les configurer et les inspecter si nécessaire pour vos tests.

Vous pouvez en effet l'utiliser pour appeler directement des méthodes doGet/doPost à partir de tests JUnit ou TestNG, et pour tester toutes les méthodes internes même si elles se réfèrent au ServletContext ou utilisent des paramètres de session (ou toute autre fonctionnalité de l'API Servlet).

Cela n'a pas besoin d'un conteneur externe ou intégré, ne vous limite pas à des tests "d'intégration" plus larges basés sur HTTP, et contrairement aux mocks à usage général, il a le comportement complet de l'API Servlet "intégré", donc vos tests peuvent être " état basé sur "plutôt que sur" interaction "(par exemple, vos tests ne doivent pas reposer sur la séquence précise des appels d'API Servlet effectués par votre code, ni sur vos propres attentes quant à la manière dont l'API Servlet répondra à chaque appel) .

Il y a un exemple simple dans ma réponse à Comment tester ma servlet en utilisant JUnit . Pour plus de détails et un téléchargement gratuit, consultez le site Web ObMimic .

0
Mike Kaufman