SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

「日本Seleniumユーザーコミュニティ」のエキスパートが教えるSelenium最新事情

Groovy製のSeleniumラッパーライブラリ「Geb」で、可読性の高いテストを書いてみよう

  • X ポスト
  • このエントリーをはてなブックマークに追加

Gebのベストプラクティス&Tips

 さて、ここまでGebの機能を押さえたら、ベストプラクティスを押さえることでGebの力をさらに引き出しましょう。ちょうどGreach2017というカンファレンスで、GebのコミッターMarcin Erdmannさんが『Geb Best Practices』を発表していますので、そちらを参考にしつつ、いくつかベストプラクティス&Tipsを紹介していきます。

わからない! とならないために

 Gebは簡潔な記述がウリですが、やり過ぎると一体何が起きているのかさっぱりわからない、あるいはテストの記述がしにくい、という問題が起き得ます。そうならないためのTipを二つ紹介します。

IntelliJ IDEAを利用する

 IntelliJ IDEAは、他のIDEよりもGebに対して特別なサポートを提供しており、下記を行ってくれます。

  • to、atといったページ遷移系メソッドの補完が効く
  • Page、Module内で定義されたコンテンツのハイライト
  • Page内のatやcontentブロックの補完が効く

 特に記述時に役立つ機能なので、Gebを利用する際はぜひIntelliJ IDEAとセットでの利用を検討してみてください。

現在のページをトラッキングする

 Gebによる簡潔なテストコードの中では、「今どのページにいるのかわからない」と混乱しがちです。そうならないための策として、今どこのページにいるのかを明示する、という方法があります。

def adminPage = to(LoginPage).login("user", "password")
adminPage.showProfile()

 上記の例では、adminPageという変数を定義し、現在のページを明示しています。簡潔さとどちらをとるかという問題はありますが、現在のページを明示するのは積極的に利用していくと良いでしょう。なお、この場合adminPage内に定義されているコンテンツやメソッドへのIDEからの補完も効くようになります。

コンテンツ定義はわかりやすく

 Navigation APIの定義は、かなり高い自由度で記述することができますが、やりすぎるとわかりにくくなってしまう場合があります。例えば、以下のように記述するとわかりにくくなります(CSS attributeセレクタを利用した書き方)。

$('button[type="select"]')

 以下のようにした方が、わかりやすい記述になります(attribute mapセレクタを利用した書き方)。

$(button, type: 'select')

 いずれの書き方でも、Geb上ではCSS attributeセレクタとして解釈されるため、実行速度の差はほとんどありません(テキストを利用した定義については例外です。この点は後述します)。よりわかりやすい記述を積極的に利用していきましょう。

もっと簡潔に記述できないか? と思ったら

 ページオブジェクトでのテストの記述に慣れてくると、より構造化やメンテナンス性を追求したコードを目指したくなります。その際にはぜひmoduleの積極的な利用を検討してみてください。

 moduleはエレメントの再利用という側面だけではなく、下記のような場面で積極的に利用していきましょう。

  • ひとまとめにして定義した方が取り回しがしやすいものを利用する(日付の入力欄など)
  • コンテンツの階層構造を隠蔽したい
  • 複雑なインタラクションを隠蔽したい

 また、moduleは慣れると非常に便利です。例えば、moduleにはvalueというメソッドが定義されており、それをOverrideすることで記述をより簡略化できます。

package modules

import geb.Module
import geb.navigator.Navigator
import org.openqa.selenium.Keys

import java.time.LocalDate
import java.time.format.DateTimeFormatter

class 日付入力欄 extends Module {
    static content = {
        日付 { $('#datePick') }
    }

    Navigator value(LocalDate date) {
        日付 = ""
        日付 << date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))
        // エンターキーを押下
        日付 << Keys.chord(Keys.ENTER)
    }

}

 上記の例では、valueをセットするメソッドをOverrideし、LocalDateで値をセットしています。valueメソッドをOverrideすると、そのエレメントに値を直接代入できるようになるため、ページオブジェクト側で、下記のように簡潔に記述できるようになります。

this.宿泊日 = 宿泊日
// this.宿泊日.value(宿泊日)と記述しても等価です

 このようにmoduleは使い方次第でより簡潔な記述ができるようになりますので、積極的に利用してみてください。

REST APIのテストがしたい場合は?

 例えば、ブログのようなサービスの場合、URLがブログ記事のIDやタイトルから生成される場合があります。このようなページへの遷移は、通常の遷移に一工夫必要です。

class EntryPage extends Page {
    Entry entry

    String getPageUrl() {
        "entries/{entry.name}"
    }
}

 上記のようにgetPageUrlメソッドをオーバーライドすると、その戻り値の相対URLに遷移するページオブジェクトを作成することができます。

 テストケースの方では、下記のように具体的な遷移先を指定します。

def entry = new Entry(name: "how-to-use-geb")
to(new EntryPage(entry: entry))

 ここでのポイントは、toはパラメータ付きのページオブジェクトを受け取ることができる、ということです。

 ここでtoについてもう少し詳しく掘り下げておきます。toの実体はBrowserクラスのprivateメソッドであるtoメソッドです。Groovyの機能(methodMissing、引数があるメソッドは()を省略できる)を利用して、簡潔な記述ができるようになっています。

 上記の例では、toがメソッドであることがよりイメージしやすい記法になっているかと思います。

テストが不安定? と思ったら

 テストを書いてしばらくすると、タイミング次第でパスしたり、落ちたりと不安定なテストが出てくるかもしれません。その際にはwaitの処理に問題がないかを確認してみましょう。

waitはページオブジェクトに隠蔽する

 Gebの強みの一つは簡潔なwaitだと冒頭に記述しましたが、waitがテストケースに記述されていると、網羅的ではないwaitの処理になりテストが不安定になっている可能性があります。

 テストケース内にwaitが記述されるのは、メンテナンスの観点からも望ましくありません。ページオブジェクト内に隠蔽しましょう。waitをページ内に記述する際は、いくつか便利機能が用意されています。

    static content = {
        asyncPageLoadButton(to: AsyncPage, toWait: true) { $("button#load-content") }
    }

 例えば上記の例では、asyncPageLoadButtonをクリックした際に、AsyncPageへのページオブジェクトの受け渡しがされ、さらにAsyncPage内のatチェックが実行されるまでwaitがかかります。

一律のwaitでは対応できない場合は?

 基本的には自サービス内で非同期処理があるので、あまりwaitのタイムアウト時間は長くなくて良い(レスポンスまでの時間が短い)が、一律にタイムアウト設定を短くしてしまうと、サードパーティの(レスポンスまでの時間が長い)APIを利用したテストが落ちてしまう、といったケースもテストを不安定にします。

 こういった場合は、waitのタイムアウトを適宜調整する必要があります。そんな場合に便利なのがpresetsという機能です。GebConfig.groovyに例えば下記のように記述します。

waiting {
    presets {
        slow {
            timeout = 20
            retryInterval = 1
        }
        quick {
            timeout = 1
            // retryIntervalはデフォルトの0.1を利用
        }
    }
}

 上記では、タイムアウトが20秒のslowという設定と、タイムアウトが1秒のquickというふたつの設定を用意しています。

 呼び出し側では、以下の形で呼び出すことができます。

waitFor("quick") { waitの条件 }

 これを利用し通常はquick、時間がかかることが予想される場合はslowを指定して、タイムアウトの時間を調整する、といったことが可能です。

テストが遅い? と思ったら

 Gebのテストケースが増えてくると、テストケースが遅いと感じることがあるかもしれません。ブラウザ自動化を伴うテストはユニットテストに比べ実行時間はかかるものですが、ちょっとした工夫で速度改善が見込める場合もあります。

reportOnTestFailureOnlyを利用する

 新機能の部分でも紹介しましたが、規模の大きなテストスイートを実行する際に、テストの成功、失敗に関わらずキャプチャを取得しているとテストの実行が遅くなりかねません。テストのキャプチャを取得した理由が、失敗時にどんな状態で失敗したいのかをチェックしたいためなのであれば、この設定を利用して不要なキャプチャの取得を防ぎましょう。

atチェックを簡潔に

 atで記述した内容は、後述するテストクラス内でのtoメソッドやatメソッドを呼び出すことで実行されます。

 このat内に記述された処理は、Gebを利用する際は非常に多く呼び出される部分です。そのため、ここに時間のかかる処理を記述指定しまうと、テスト全体の実行時間に大きな影響を与えてしまいます。そのため、ページ内の処理のチェックなどはせず、あくまで「現在意図したページを表示しているか」という観点での最低限のチェックを行うようにしてください。

エレメントの取得をテキストで行うのを避ける

 Gebのエレメントの取得は、基本的にCSSセレクターを利用して行なっていますが、テキストを利用したエレメントの取得だけはGebの独自拡張となっています。これは利用時にhttpリクエストを多数発行してしまう実装になっており、実行速度に悪影響を与えます。

 便利なのでつい多用してしまいがちなのですが、極力利用しない、利用するとしても、可能な限り他の条件を指定して絞り込んでから利用するようにしてください。

次のページ
おわりに

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
「日本Seleniumユーザーコミュニティ」のエキスパートが教えるSelenium最新事情連載記事一覧

もっと読む

この記事の著者

高橋 陽太郎(株式会社リクルートテクノロジーズ)(タカハシ ヨウタロウ)

 リクルートテクノロジーズ ITエンジニアリング本部 HR領域エンジニアリング部 RJBグループ グループマネジャー ERPパッケージベンダー、勤怠SaaSエンジニアを経て、リクルートテクノロジーズ入社。現在はHR領域のエンジニアリンググループのマネージャーとして、タウンワークをはじめとしたサービスの開発リーダーを担っている。 コミュニティとして、TDDBCやSeleniumユーザーコミュニティの運営に携わっている。その傍ら、副業として企業へのSelenium導...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/10456 2017/10/06 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング