ブラウザテスト自動化、してますか?

56fea137fee205d9dab03096552ef589_s

こんにちは。PoohSunnyです。
アジャイルな方法論がたくさんの現場で導入されはじめ、高頻度リリースを志向する現場が増えてきています。
その流れで、『毎回手動でテストなんかしてられない!』ということでテストを一部自動化するケースが増えています。

自動化されるテストにはさまざまな種類があります。例えばブラウザの自動化テスト。
この分野はSeleniumが有名ですね。今日はそれにまつわる話です。

Seleniumはときに辛い

Seleniumでのブラウザテストの自動化は、時に辛いことに遭遇します(控えめに書いています)。
例として、ちょっと古いバージョンの例で恐縮ですが、Javaで書いたサンプルを用意しました。
Googleの検索欄に文字を入れて、検索結果を表示するスクリプトです。


public class sampleTest { WebDriver driver; @Before public void setUp() { driver = new FirefoxDriver(); } @Test public void googleSuggestTest() { // Googleのページへ遷移 driver.get("http://www.google.com"); // 他のやり方として、以下のように記述することもできます // driver.navigate().to("http://www.google.com"); // nameからtext inputのエレメントを探す WebElement element = driver.findElement(By.name("q")); // 検索するために何か入力 element.sendKeys("Cheese!"); // フォームをサブミット。WebDriverはエレメントからフォームを見つけてくれます。 element.submit(); // ページのタイトルをチェック assertThat(driver.getTitle(), is("Google")); // Google検索はJavaScriptで動的に表示されます // ページがロードされるのを待ちます。タイムアウトは10秒 (new WebDriverWait(driver, 10)).until(new ExpectedCondition() { public Boolean apply(WebDriver d) { return d.getTitle().toLowerCase().startsWith("cheese!"); } }); // "Cheese!"から始まる文字が表示されているべき assertThat(driver.getTitle(), startsWith("Cheese!")); } @After public void tearDown() { // ブラウザを閉じる driver.quit(); } }

やりたい操作自体は単純なはずですが、その割にコードが縦横に長いですよね。
自動テストを回数多く頻繁に実行しようとするなら、テストは長期的にメンテが必要になります。
記述の冗長性などは、メンテナンスの難易度を大きく左右します。

もっと楽をする方法

というわけで、いろいろな人がSeleniumを楽して書く努力をしており、さまざまなフレームワークがリリースされています。
今回はその中でも、私がイチオシのGebを紹介します。Groovy製でJavaとの相性も良いので、今プロダクションコードをJavaで書いている方は触ってみるといいですよー!

Gebで楽しよう!

さっきのSeleniumのコードをGeb&Spockで書き直すと?

こんなコードになります。

class SampleSpec extends GebReportingSpec {

    def "GoogleSuggestのテスト"() {
        when: 'Googleのページへ遷移'
        go "http://www.google.com"

        and: '検索欄を探し、入力'
        def element = $("input", name: "q")
        element !! "Cheese!"

        then: "ページのタイトルをチェック"
        assert title == "Google"

        when: "フォームをサブミット。(ここではエンターキーの入力で代替)"
        element.value(Keys.ENTER)

        // Google検索はJavaScriptで動的に表示されます
        // ページがロードされるのを待ちます。タイムアウトは10秒(GebConfigで変更可能)
        waitFor { title.toLowerCase().startsWith("cheese!") }

        then: "Cheese!から始まる文字が表示されているべき"
        assert title.startsWith("Cheese!")
    }
}

ぱっと見でも、コードがかなり短くなったと思います。詳しく見ていきますね。

Gebはここがすばらしい!

初期化・終了処理が不要

Javaだと、WebDriverの初期処理、終了処理を書かねばなりません。

    WebDriver driver;

    @Before
    public void setUp() {
        driver = new FirefoxDriver();
    }

    // 略

    @After
    public void tearDown() {
        // ブラウザを閉じる
        driver.quit();
    }    

各テストクラスに書くの面倒なので、ベースクラスを作成してそこに書いたりしますが、それでも面倒ですね。
Gebの場合は、GebSpecというベースクラスがもともと用意されているので、各テストクラスはそれを継承するだけで初期化・終了処理を書く必要がありません。

Driverの初期化が楽ちん

ブラウザの自動化テストだと、必然的にマルチブラウザのテストが必要になります。Seleniumでそれをやるのは少々手間です。
Gebは、ドライバーの初期処理をGebConfig.groovyという設定ファイルで記述します。起動引数でドライバを変更したり、複数ドライバを順番にテストする、とかも簡単にできます。
サンプルは下記のようになります。

environments {
    chrome { // Chromeのテスト
        driver = { new ChromeDriver() }
    }
    firefox { // Firefoxのテスト
        driver = { new FirefoxDriver() }
    }
    phantomJs { // phantomJsのテスト
        driver = { new PhantomJSDriver() }
    }
}

given-when-thenが使える

ブラウザテストは、何を操作しているのかをわかりやすくするため、コメントを打つ(打ちたくなる)ことがあります。

        // Googleのページへ遷移
        driver.get("http://www.google.com");

        // nameからtext inputのエレメントを探す
        WebElement element = driver.findElement(By.name("q"));

ただ、これだとスクリプト本体が見にくくなり、何をテストしたいのかが不明確になります。
今回はGebと一緒にGroovy製のテスティングフレームワークSpockを使いますが、Spockには、「given-when-then」のGherkin記法でテストを記述できるという機能があります。
SpockのGherkin記法を使うと、この点がうまくブロック化され可読性が増します。

        when: "Googleのページへ遷移" 
        go "http://www.google.com"

        and: "検索欄を探し、入力" 
        def element = $("input", name: "q";)
        element.value(”Cheese!”)

「何をテストしたいのか」をかなり意識するようになるので、書いてて気持ちよくなります。

実際には、Gebを利用するにしても、Seleniumを利用するにしても、冗長化してしまう記述はでてきます。
その場合はヘルパーメソッドを書いたり、ページオブジェクトパターンを利用してなるべく意図の伝わりやすいコードを書くべきです。

記述が簡潔になる

Groovyの機能を存分に使って、JavaのSeleniumでは冗長になりがちな記法が簡潔になります。比較をいくつか。

        driver.get("http://www.google.com"); // Selenium

        go "http://www.google.com" // Geb
        WebElement element = driver.findElement(By.name("q")); // Selenium

        def element = $("input", name: "q") // Geb jQueryっぽい書き方が気持ちいい
        element.sendKeys("Cheese!"); // Selenium

        element.value(”Cheese!”) // Geb
        (new WebDriverWait(driver, 10)).until(new ExpectedCondition() {
            public Boolean apply(WebDriver d) {
                return d.getTitle().toLowerCase().startsWith("cheese!");
            }
        }); // ここまでSelenium Java8使うともう少し簡潔になります

        waitFor { title.toLowerCase().startsWith("cheese!") } // Geb

Gebためそう!!

駆け足でしたが、Gebの素晴らしさを紹介してみました。

興味湧いた?(宣伝)

このGebの話で、JJUG CCCというカンファレンスのセッションにエントリーしてます。
これ、みんなの投票を参考にセッションが採択されるっぽいので、このエントリーで興味が湧いた人は、ぜひ私のセッションに投票をお願いします!!(宣伝)