ページオブジェクトの作成
ここまででGebを実行する準備が整いました。続いてページオブジェクトを作成します。
ページオブジェクトとは何かについては、Selenideの記事に詳しい説明がありますのでそちらをご参照ください。
Gebのページオブジェクト
Gebはページオブジェクトを標準でサポートしています。
テスト用サイトの予約入力画面のページオブジェクトを記載すると、下記のようになります。
package pages import geb.Page import modules.日付入力欄 import java.time.LocalDate class 予約入力ページ extends Page { static at = { $('h1').text() == '予約フォーム' } static content = { 宿泊日 { module 日付入力欄 } 泊数 { $('#reserve_term') } 人数 { $('#headcount') } // 略 名前 { $('#guestname') } 利用規約に同意して次へ { $("#agree_and_goto_next") } } def 予約内容の入力(LocalDate 宿泊日, int 泊数, String 名前) { this.宿泊日 = 宿泊日 this.泊数 << String.valueOf(泊数) this.名前 << 名前 } }
Gebのページオブジェクトは、Pageという基底クラスを継承し、実装することで実現されます。
今回はテストクラス、またその中のプロパティを可能な限り日本語で記述しています。これは、可読性を考えた際に、日本語の方がよりわかりやすいと考えたためです。IDEの補完という側面からは多少の煩わしさがありますが、プログラムは書いているより読んでいる時間が多いと一般に言われていますから、可読性をここでは重視しています。英語の方が読みやすい場合は、もちろん英語で記述することも可能です。
ページオブジェクト内の記述について、順に見ていきます。
at
ページクラスの冒頭に記述されているatには、現在そのページを表示しているかをチェックする処理を記述します。このサンプルでは、ページ内のh1テキストが「予約フォーム」になっているかをチェックしています。もしこの条件に該当しない場合は例外を投げてテストが終了します(設定で動作の変更が可能です)。
content
contentは、そのページ内に存在するエレメントを定義する場所です。下記のような形式で記述します。
エレメント名 { エレメントを取得するための記述 }
エレメントの取得には、前述のNavigation APIを利用することができます。このサンプルでは、全てid指定で要素を取得しています。
module
content内に下記の記述があります。
宿泊日 { module 日付入力欄 }
これはGebのmoduleという機能を利用しています。moduleとは、再利用可能なエレメントを定義したもので、例えばWebサイトのヘッダーなどを定義する時に利用します。
今回のサンプルでは一か所でしか利用していないため厳密には不要でしたが、再利用性を考慮した場合のサンプルとして、カレンダーでの日付選択欄をmoduleとして定義しています。moduleをページオブジェクト内で利用するには、下記のように記述します。
エレメント名 { module モジュールのクラス名 }
メソッド
Gebは、上記のcontentのように、簡潔にエレメントが定義できます。このことからエレメントをテストケースから直接参照してしまうことも多く、その場合はエレメントを変更した際にテストケースの修正量が増えてしまいます。これへの対策として、まとまった処理は極力メソッドとして定義する方が良いでしょう。
サンプルでは、予約内容の入力をメソッドとして定義しています。
予約確認画面のページオブジェクト
予約入力画面に続き、予約確認画面のページオブジェクトも作成します。
package pages import geb.Page import java.time.LocalDate import java.time.format.DateTimeFormatter class 予約内容確認ページ extends Page { static at = { $("h1").text() == "予約内容" } static content = { 宿泊日 { $('#datefrom').text() } 泊数 { $('#dayscount').text() } // 略 名前 { $('#gname').text() } 合計金額 { $('#price').text() } } }
エレメント内のキャプションを表示するには.text()
でアクセスします。
テストケースの記載
ページオブジェクトの作成が完了したので、ページオブジェクトを利用してテストケースを作成します。
package specs import geb.spock.GebReportingSpec import pages.予約入力ページ import pages.予約内容確認ページ import java.time.LocalDate import java.time.format.DateTimeFormatter class 予約のテスト extends GebReportingSpec { def "予約内容を不備なく入力すると、確認画面で入力した内容と金額が確認できる"() { given: "テストで利用するデータをセットアップし" def 宿泊日 = LocalDate.of(2017, 9, 25) def 泊数 = 2 def 名前 = "東京太郎" and: "予約入力ページに遷移している" def 予約入力ページ = to 予約入力ページ when: "予約内容を入力して" 予約入力ページ.予約内容の入力(宿泊日, 泊数, 名前) and: "利用規約に同意して次へボタンを押すと" 予約入力ページ.利用規約に同意して次へ.click() then: "予約内容ページに遷移し" def 予約内容確認ページ = at 予約内容確認ページ and: "入力した内容が表示されていて" 予約内容確認ページ.宿泊日 == 宿泊日.format(DateTimeFormatter.ofPattern("yyyy年M月dd日")) 予約内容確認ページ.泊数 == String.valueOf(泊数) 予約内容確認ページ.名前 == 名前 and: "金額は16000円" 予約内容確認ページ.合計金額 == "16000" } }
特有の記述について、順に説明していきます。
given-when-then-and
テスト中に記述されているgiven-when-thenはSpockのブロックという機能です。テストケース内でどこになんの処理が書かれているのかがわかりやすくなります。詳細はSpockのドキュメントを参照してください。
- given(setup):事前準備を記述します。
- when:実際にテスト対象のアクションを記述します。ボタンのクリックなどを記述することになるでしょう。
- then:実際に帰ってきた値をアサートします。thenブロック内の記述は、自動的にアサートとして扱われ、もしもthenブロック内の条件がfalseと評価されるとテストケースは失敗として扱われます。
- and:直前のブロックと同じ意味を持ちます。
ページ遷移の処理(to、via)
def 予約入力ページ = to 予約入力ページ
ページの遷移の処理はtoまたはviaを利用します。上記の処理は、予約入力ページへ遷移し、そのページオブジェクトを宣言されている変数に代入してます。
toとvia
toとviaはよく似たメソッドですが、これらは下記の違いがあります。
- to:画面遷移後に、遷移先のページのatブロック内で定義されたチェックが実行される
- via:画面遷移後に、遷移先のページのatブロック内で定義されたチェックが実行されない
使い分けですが、例えば入力内容などによって遷移先が動的に変わるような場合はviaメソッドを使うと良いでしょう。
at
def 予約内容確認ページ = at 予約内容確認ページ
このatメソッドを呼ぶことで、明示的にそのページ内のatチェックが実行されます。該当ページへの遷移をtoメソッドで行う場合は重複する処理となりますが、該当したページに正しく遷移したかを明示的に確認する場合は、処理の重複となってもatメソッドを記述した方がより可読性の高いテストになります。
ここまでで、一つのテストケースが書き終わりました。