54

サーブレットの単体テストを行う最良の方法を知りたいです。

内部メソッドのテストは、サーブレット コンテキストを参照しない限り問題ありませんが、コンテキストを参照したりセッション パラメータを使用したりする doGet/doPost メソッドや内部メソッドのテストはどうでしょうか。

JUnit、できればTestNGなどの従来のツールを使用してこれを行う方法はありますか? Tomcat サーバーなどを埋め込む必要がありましたか?

4

8 に答える 8

47

ほとんどの場合、純粋な単体テストではなく「統合テスト」を介してサーブレットと JSP をテストします。JUnit/TestNG には、次のような多数のアドオンが用意されています。

  • HttpUnit (最も古く、最もよく知られている、非常に低いレベルであり、ニーズに応じて良い場合も悪い場合もあります)
  • HtmlUnit (多くのプロジェクトに適した HttpUnit よりも高いレベル)
  • JWebUnit (他のテスト ツールの上に位置し、それらを単純化しようとします - 私が好むもの)
  • WatiJと Selenium (ブラウザーを使用してテストを行います。これはより重いですが現実的です)

これは、フォーム「orderEntry.html」からの入力を処理する単純な注文処理サーブレットの JWebUnit テストです。顧客 ID、顧客名、および 1 つ以上の注文項目が必要です。

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;
}
于 2008-09-18T09:11:56.543 に答える
13

HttpUnitを試してください。ただし、(単一クラスの)「単体テスト」よりも(モジュールの)「統合テスト」に近い自動テストを作成することになる可能性があります。

于 2008-09-18T08:22:57.270 に答える
11

投稿された回答を見て、組み込みの GlassFish とその Apache Maven プラグインを使用してテストを行う方法を実際に示す、より完全なソリューションを投稿すると思いました。

ブログのUsing GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.xに完全なプロセスを書き、完全なプロジェクトを Bitbucket のダウンロード用に配置しました: image-servlet

この質問を見る直前に、JSP/JSF タグのイメージ サーブレットに関する別の投稿を見ていました。そこで、他の投稿で使用したソリューションと、この投稿の完全な単体テスト バージョンを組み合わせました。

テスト方法

Apache Maven には、 を含む明確に定義されたライフサイクルがありますtestintegration-testこれを、ソリューションを実装するために呼び出される別のライフサイクルと共に使用します。

  1. Surefire プラグインで標準のライフサイクル ユニット テストを無効にします。
  2. integration-testSurefire-plugin の実行の一部として追加します。
  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>

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

ブログのUsing GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.xに完全なプロセスを書き、完全なプロジェクトを Bitbucket のダウンロード用に配置しました: image-servlet

ご不明な点がございましたら、コメントを残してください。これは、サーブレットのテストを計画する際の基礎として使用できる 1 つの完全な例だと思います。

于 2013-01-06T01:54:00.570 に答える
7

単体テストで 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   
   }
}

モック作成メソッドを基本サーブレット スーパークラスに配置し、すべてのサーブレット ユニット テストを拡張してそれを拡張することは、さらに優れています。

于 2008-09-18T08:43:28.297 に答える
6

Mockrunner ( http://mockrunner.sourceforge.net/index.html ) でこれを行うことができます。サーブレットのテストに使用できるモック J2EE コンテナーを提供します。EJB、JDBC、JMS、Struts などの他のサーバー側コードの単体テストにも使用できます。私自身は、JDBC と EJB の機能しか使用していません。

于 2008-09-18T16:06:13.113 に答える
3

HttpRequestサーブレット doPost() メソッドの JUnit テストのこの実装は、 、HttpResponseHttpSessionServletResponseおよびのインスタンスをモックアップするために Mockito ライブラリのみに依存しています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);
    }
}
于 2016-07-26T12:15:58.003 に答える
0

2018 年 2 月更新: OpenBrace Limited は閉鎖され、その ObMimic 製品はサポートされなくなりました。

もう 1 つの解決策は、サーブレットの単体テスト用に特別に設計されたObMimicライブラリを使用することです。すべてのサーブレット API クラスの完全なプレーン Java 実装を提供し、テストの必要に応じてこれらを構成および検査できます。

実際、JUnit または TestNG テストから doGet/doPost メソッドを直接呼び出したり、ServletContext を参照したり、セッション パラメーター (または他のサーブレット API 機能) を使用したりする場合でも、内部メソッドをテストするために使用できます。

これは、外部または埋め込みコンテナーを必要とせず、より広範な HTTP ベースの「統合」テストに制限されません。また、汎用モックとは異なり、完全なサーブレット API 動作が「組み込まれている」ため、テストを「 「相互作用」ベースではなく「状態」ベース (たとえば、テストは、コードによって行われるサーブレット API 呼び出しの正確なシーケンスに依存する必要も、サーブレット API が各呼び出しにどのように応答するかについての独自の期待に依存する必要もありません) .

How to test my servlet using JUnitに対する私の回答には、簡単な例があります。詳細と無料ダウンロードについては、ObMimicのWeb サイトを参照してください。

于 2013-08-15T12:35:00.150 に答える