12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MockMvc + HtmlUnitでE2Eテストをブラウザ・APサーバレスに!

Last updated at Posted at 2017-10-15

今回は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テストのハードルを低くして、手作業でのテストを無くしていけると良いですね!

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?