HtmlUnit 統合の理由

頭に浮かぶ最も明白な質問は、「なぜこれが必要なのですか ? 」です。答えは、非常に基本的なサンプルアプリケーションを調べることで最もよくわかります。Message オブジェクトでの CRUD 操作をサポートする Spring MVC Web アプリケーションがあるとします。このアプリケーションは、すべてのメッセージのページングもサポートしています。テストしてみてはどうですか?

Spring MVC Test を使用すると、次のように Message を作成できるかどうかを簡単にテストできます。

  • Java

  • Kotlin

MockHttpServletRequestBuilder createMessage = post("/messages/")
		.param("summary", "Spring Rocks")
		.param("text", "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
		.andExpect(status().is3xxRedirection())
		.andExpect(redirectedUrl("/messages/123"));
@Test
fun test() {
	mockMvc.post("/messages/") {
		param("summary", "Spring Rocks")
		param("text", "In case you didn't know, Spring Rocks!")
	}.andExpect {
		status().is3xxRedirection()
		redirectedUrl("/messages/123")
	}
}

メッセージを作成できるフォームビューをテストする場合はどうでしょうか。例: フォームが次のスニペットのように見えると仮定します。

<form id="messageForm" action="/messages/" method="post">
	<div class="pull-right"><a href="/messages/">Messages</a></div>

	<label for="summary">Summary</label>
	<input type="text" class="required" id="summary" name="summary" value="" />

	<label for="text">Message</label>
	<textarea id="text" name="text"></textarea>

	<div class="form-actions">
		<input type="submit" value="Create" />
	</div>
</form>

フォームが新しいメッセージを作成するための正しいリクエストを生成することをどのように確認しますか? 素朴な試みは次のようになります。

  • Java

  • Kotlin

mockMvc.perform(get("/messages/form"))
		.andExpect(xpath("//input[@name='summary']").exists())
		.andExpect(xpath("//textarea[@name='text']").exists());
mockMvc.get("/messages/form").andExpect {
	xpath("//input[@name='summary']") { exists() }
	xpath("//textarea[@name='text']") { exists() }
}

このテストにはいくつかの明らかな欠点があります。text の代わりにパラメーター message を使用するようにコントローラーを更新すると、HTML フォームがコントローラーと同期していない場合でも、フォームテストに合格し続けます。これを解決するために、次のように 2 つのテストを組み合わせることができます。

  • Java

  • Kotlin

String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
		.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
		.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());

MockHttpServletRequestBuilder createMessage = post("/messages/")
		.param(summaryParamName, "Spring Rocks")
		.param(textParamName, "In case you didn't know, Spring Rocks!");

mockMvc.perform(createMessage)
		.andExpect(status().is3xxRedirection())
		.andExpect(redirectedUrl("/messages/123"));
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
	xpath("//input[@name='$summaryParamName']") { exists() }
	xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
	param(summaryParamName, "Spring Rocks")
	param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
	status().is3xxRedirection()
	redirectedUrl("/messages/123")
}

これにより、テストが不合格になるリスクが軽減されますが、まだいくつかの問題があります。

  • ページに複数のフォームがある場合はどうなるでしょうか? 確かに、XPath 式を更新できますが、より多くの要因を考慮すると、式はより複雑になります。フィールドは正しい型ですか? フィールドは有効になっていますか?

  • もう 1 つの課題は、予想される作業の 2 倍を行っていることです。最初にビューを検証し、次に検証したばかりの同じパラメーターでビューを送信する必要があります。理想的には、これは一度にすべて実行できます。

  • 最後に、まだいくつかのことを説明できません。例: フォームに JavaScript 検証があり、それもテストしたい場合はどうなるでしょうか?

全体的な問題は、Web ページのテストに単一の対話が含まれないことです。代わりに、ユーザーが Web ページと対話する方法と、その Web ページが他のリソースと対話する方法の組み合わせです。例: フォームビューの結果は、メッセージを作成するためのユーザーへの入力として使用されます。さらに、フォームビューでは、JavaScript 検証など、ページの動作に影響を与える追加のリソースを潜在的に使用できます。

統合テストで解決?

前述の課題を解決するには、エンドツーエンドの統合テストを実行できますが、これにはいくつかの欠点があります。メッセージをページングできるビューのテストを検討してください。次のテストが必要になる場合があります。

  • メッセージが空の場合、結果が利用できないことを示す通知がページに表示されますか?

  • ページに単一のメッセージが適切に表示されていますか?

  • ページはページングを適切にサポートしていますか?

これらのテストを設定するには、データベースに適切なメッセージが含まれていることを確認する必要があります。これは、いくつかの追加の課題につながります。

  • 適切なメッセージがデータベースにあることを確認するのは面倒です。(外部キー制約を考慮してください。)

  • 各テストではデータベースが正しい状態であることを確認する必要があるため、テストが遅くなる可能性があります。

  • データベースは特定の状態にする必要があるため、テストを並行して実行することはできません。

  • 自動生成された ID、タイムスタンプなどの項目に対してアサーションを実行するのは難しい場合があります。

これらの課題は、エンドツーエンドの統合テストを完全に放棄する必要があるという意味ではありません。代わりに、詳細テストをリファクタリングして、より速く、より信頼性が高く、副作用のないモックサービスを使用することで、エンドツーエンドの統合テストの数を減らすことができます。その後、単純なワークフローを検証して、すべてが適切に連携することを確認する少数の真のエンドツーエンド統合テストを実装できます。

HtmlUnit 統合を開始

それでは、どのようにしてページの相互作用をテストし、それでもテストスイート内で良好なパフォーマンスを維持することのバランスをとることができますか? 答えは、「MockMvc と HtmlUnit を統合することです。」

HtmlUnit 統合オプション

MockMvc と HtmlUnit を統合する場合、いくつかのオプションがあります。

  • MockMvc および HtmlUnit : 生の HtmlUnit ライブラリを使用する場合は、このオプションを使用します。

  • MockMvc および WebDriver : このオプションを使用して、統合テストとエンドツーエンドテストの間でコードの開発と再利用を容易にします。

  • MockMvc および Geb : Groovy をテストに使用し、開発を容易にし、統合テストとエンドツーエンドテストの間でコードを再利用する場合は、このオプションを使用します。