今回はWebアプリケーションのE2Eテストについてお話します。
E2Eテストを自動化するときにはSeleniumを使うことが多いですが、実行までの準備やテストコードのメンテナンスに思った以上の労力がかかることもしばしば。
Spring TestのMockMVCとHTMLUnitを組み合わせることで、ブラウザ・APサーバレスに効率的なテストの実施が可能です。
はじめに
Seleniumを利用したE2Eテストの問題点(大変さ)
- WarファイルのAPサーバへのデプロイが必須
- Seleniumのバージョンに合ったブラウザが必要(=ブラウザの固定化)
- ブラウザのバージョンに合わせたWebDriverドライバが必要(=環境準備の必要性)
※WebDriverManagerの導入によって、ドライバ入手の負担は減るかもしれません。 - ブラウザの起動に時間がかかり、テスト実行のオーバーヘッドが大きい
- ブラウザおよびバージョンごとに挙動が違う(=テストコードカスタマイズの必要性)
MockMvcとHTMLUnitを利用することで、これらの問題から解放されコーディングに集中することができます!
MockMvcとは?
MockMVCとは、Spring Testで提供されており、Spring MVCのWebアプリケーションをモック化してコントローラのテストを行う仕組みです。
コントローラをスタンドアロンで動作させる方法と、アプリケーションコンテキストを読み込んでWebアプリケーションとして動作させる方法がありますが、今回は後者を使用します。
HtmlUnitとは?
HtmlUnitとは、ブラウザをシミュレートしてHTMLのテストを行う仕組みで、JavaScriptの挙動までテストすることが可能です。
ブラウザをシミュレートするためのインターフェイスとして、HtmlUnit独自のWebClientが提供されていますが、今回はSelemium WebDriverと組み合わせてWebDriverを使用します。
今回利用したライブラリのバージョン
- Spring IO Platform Athens-SR2 で管理されるバージョン
MockMVC + HTMLUnitのセットアップ
3ステップで完了です。
Maven POMのセットアップ
まずは依存関係をセットアップします。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
No. | Description |
---|---|
(1) | MockMvcを利用するため、spring-test を追加します。 |
(2) | MockMvcでSpring Securityを利用するときは、spring-security-test を追加します。 |
(3) | HtmlUnitとSelenium WebDriverを利用するため、selenium-java を追加します。 |
MockMvcのセットアップ
次に、テストコードでMockMvcを使うためのセットアップを行います。
@RunWith(SpringJUnit4ClassRunner.class) // (1)
@WebAppConfiguration // (2)
@ContextHierarchy({ // (3)
@ContextConfiguration({"classpath*:web-application-context.xml",
"classpath*:spring-security-context.xml" }),
@ContextConfiguration("classpath*:servlet-context.xml") })
public class MockMvcHtmlUnitTest {
@Autowired
private WebApplicationContext context; // (4)
@Before
public void setup() {
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context) // (5)
.addFilters(new CharacterEncodingFilter("UTF-8", true)) // (6)
.apply(springSecurity()) // (7)
.alwaysDo(log()) // (8)
.build(); // (9)
}
}
No. | Description |
---|---|
(1) | テストケースでSpring Testの機能を有効にするため、SpringJUnit4ClassRunner を使用します。 |
(2) |
@WebAppConfiguration を付与することで、テストケースにWebApplicationContext をインジェクションすることができるようになります。 |
(3) |
@ContextConfiguration で、アプリケーションが読み込むコンテキストファイルを指定します。 @ContextConfiguration には複数のファイルを指定できますが、すべて一つのコンテキストとして展開されます。 コンテキストを階層化したいときは、@ContextHierarchy を使用します。 |
(4) | MockMvcをセットアップするため、WebApplicationContext をインジェクションします。 |
(5) |
MockMvcBuilders#webAppContextSetup() で、Webアプリケーションとして動作させたコントローラにアクセスするMockMvcをセットアップすることができます。 |
(6) | さらにaddFilters() で、サーブレットフィルタを追加することができます。(web.xmlでのセットアップを再現することができます。) |
(7) | さらにapply(springSecurity()) で、Spring Securityを有効にします。 springSecurity() はデフォルトでSpring SecurityのBeanspringSecurityFilterChain を読み込んでくれますが、異なるBeanを読み込みたいおときは引数でセットすることもできます。 テスト内容によっては、@MockUser を併用したり、Spring Securityを無効にするなど柔軟に対応すると良いですね。 |
(8) | さらにalwaysDo(log()) でModelや返却されるViewのロギングを有効にします。 log() と同様の働きをするprint() もありますが、print() が常に標準出力にロギングするのに対して、log() はロガーの設定に応じてロギングする/しないや出力先を変更することができるので、オススメです。 なお、MockMvcのロギングはログカテゴリorg.springframework.test.web.servlet.result で行われます。 |
(9) | 最後にbuild() でMockMvcインスタンスを生成します。 |
※ちなみに、、、Spring Bootなら、以下のようにシンプルにセットアップできます。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcHtmlUnitTest {
HtmlUnitのセットアップ
最後に、テストコードでHtmlUnitとSelenium WebDriverを組み合わせたHtmlUnitDriverを使うためのセットアップを行います。
private WebDriver driver;
@Before
public void setup() {
MockMvc mvc = // omitted configure MockMvc.
driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(mvc) // (1)
.contextPath("/sample-app") // (2)
.build(); // (3)
}
No. | Description |
---|---|
(1) |
MockMvcHtmlUnitDriverBuilder#mockMvcSetup() で、HtmlUnitのWebDriverからMockMvcを経由してリクエストを送信することができるようになります。 |
(2) | さらにcontextPath() で、モック化したアプリケーションのコンテキストパスを指定することができます。 MockMvcを使用するだけなら必要ありませんが、実際のアプリケーションと条件を揃えるため、指定しておくことをオススメします。 これにより、テストコードの再利用性が高まります。 |
(3) | 最後にbuild() でHtmlUnitDriverインスタンスを生成します。 |
MockMVC + HTMLUnitの使い方
普通にSelenium WebDriverを使用する場合と同様です。
さきほどセットしたコンテキストパスを意識するだけでOK。
@Test
public void testController() throws Exception {
driver.get("http://localhost/sample-app/test-path");
String text = driver.findElement(By.id("text")).getText();
assertThat(text, is("application is loaded!"));
}
※ちなみに、、、MockMvcだけを利用したときのようにModelやView名を取得することはできません。
補足
特定のブラウザをシミュレートしたいとき
HtmlUnitはブラウザバージョンを指定することで、特定のブラウザをシミュレートすることができます。
ブラウザバージョンを指定するときはHtmlUnitのセットアップを以下のように変更します。
@Before
public void setup() {
WebClient client = new WebClient(BrowserVersion.CHROME); // (1)
WebConnectionHtmlUnitDriver driver = new WebConnectionHtmlUnitDriver(BrowserVersion.CHROME); // (2)
driver.setWebConnection(new MockMvcWebConnection(mvc, client, "/sample-app")); // (3)
}
No. | Description |
---|---|
(1) |
MockMvcWebConnection を利用するため、WebClient インスタンスを生成します。 このとき、引数としてBrowserVersion を指定します。 |
(2) |
MockMvcWebConnection を利用するため、WebConnectionHtmlUnitDriver インスタンスを生成します。 ここでも、引数としてBrowerVersion を指定します。 |
(3) |
MockMvcHtmlUnitDriverBuilder#mockMvcSetup() の代わりに、MockMvcWebConnection を利用してMockMvcと連携します。 第三引数としてコンテキストパスを指定することができます。 |
まとめ
今回はMockMvcとHtmlUnitを組み合わせて、ブラウザ・APサーバレスにE2Eテストを行う方法について説明しました。
実際には、ローカル環境でのテストではブラウザ・APサーバレスでも、正式な本番向けのテストでは実際のサポートブラウザ・APサーバを使用することになると思いますが、WebDriverインターフェイスを利用することで本番向けテストへのテストコード再利用性も期待できます。(私はSpringのプロファイル機能で切り替えられるようにして使っています!)
E2Eテストのハードルを低くして、手作業でのテストを無くしていけると良いですね!