質問
サーブレットの単体テストを行う最適な方法を知りたいです。
内部メソッドのテストは、サーブレット コンテキストを参照しない限り問題ありませんが、コンテキストを参照したりセッション パラメータを使用したりする内部メソッドだけでなく、doGet/doPost メソッドをテストする場合はどうでしょうか。
JUnit やできれば TestNG などの従来のツールを使用してこれを簡単に行う方法はありますか?Tomcat サーバーなどを組み込む必要がありましたか?
解決
試す HTTPユニット, ただし、(単一クラスの)「単体テスト」よりも(モジュールの)「統合テスト」に近い自動テストを作成することになる可能性が高くなります。
他のヒント
ほとんどの場合、私は純粋な単体テストではなく、「統合テスト」を通じてサーブレットと JSP をテストします。JUnit/TestNG には、次のような多数のアドオンが利用可能です。
- HTTPユニット (最も古くて最もよく知られている、非常に低いレベルですが、ニーズに応じて良くも悪くもなります)
- HTMLユニット (HttpUnit よりも高いレベルであり、多くのプロジェクトに適しています)
- JWebUnit (他のテストツールの上に置いて、それらを簡素化しようとしています - 私が好むツールです)
- ワティジ および 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;
}
投稿された回答を見て、埋め込み GlassFish とその Apache Maven プラグインを使用してテストを実行する方法を実際に示す、より完全なソリューションを投稿しようと思いました。
完全なプロセスをブログに書きました JUnit 4.x および HtmlUnit 2.x に埋め込まれた GlassFish 3.1.1 の使用 そして、Bitbucket にダウンロードできる完全なプロジェクトをここに配置します。 画像サーブレット
この質問を見る直前に、JSP/JSF タグのイメージ サーブレットに関する別の投稿を見ていました。そこで、他の投稿で使用したソリューションを、この投稿用の完全な単体テスト済みバージョンと組み合わせました。
テスト方法
Apache Maven には明確に定義されたライフサイクルがあり、以下のものが含まれます。 test
. 。これを別のライフサイクルと一緒に使用します。 integration-test
私のソリューションを実装するために。
- surefire プラグインの標準ライフサイクル単体テストを無効にします。
- 追加
integration-test
surefire-plugin の実行の一部として - 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 の使用 そして、Bitbucket にダウンロードできる完全なプロジェクトをここに配置します。 画像サーブレット
ご質問がございましたら、コメントを残してください。これは、サーブレットに対して計画しているテストの基礎として使用できる完全な例の 1 つであると思います。
単体テストで 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 製品はサポートされなくなりました。
別の解決策は、私の オブミミック サーブレットの単体テスト用に特別に設計されたライブラリです。これは、すべてのサーブレット API クラスの完全なプレーン Java 実装を提供し、テストに必要に応じてこれらを構成および検査できます。
実際、これを使用して、JUnit または TestNG テストから doGet/doPost メソッドを直接呼び出したり、内部メソッドが ServletContext を参照したり、セッション パラメーター (またはその他のサーブレット API 機能) を使用したりする場合でも、テストすることができます。
これには外部コンテナや埋め込みコンテナは必要なく、より広範な HTTP ベースの「統合」テストに制限されません。また、汎用モックとは異なり、完全なサーブレット API の動作が「組み込まれている」ため、テストは「 「インタラクション」ベースではなく「状態」ベース(例:テストは、コードによって行われるサーブレット API 呼び出しの正確なシーケンスや、サーブレット API が各呼び出しにどのように応答するかについての独自の期待に依存する必要はありません)。
私の答えには簡単な例があります JUnit を使用してサーブレットをテストする方法. 。詳細と無料ダウンロードについては、以下を参照してください。 オブミミック Webサイト。