Edited at

Groovyのちょっとしたこと「Gebによるテストについて」

More than 3 years have passed since last update.

(2016年06月 追記 ココカラ)

この記事で紹介しているFireFoxを使ったデモは、FireFox 46以降正常に動作しないようです。

この記事紹介時点ではFireFoxさえインストールしてあればGebで操作することが可能でしたがFireFox 46以降では、https://developer.mozilla.org/ja/docs/Mozilla/QA/Marionette こちらのMarionetteをインストールし、FireFoxのドライバーとしてMarionetteWebDriverクラスを設定する必要があります。

これらの環境をそろえて該当のコードを修正すれば、記事のコードの大部分はそのまま使うことができるはずです。

(2016年06月 追記 ココマデ)

(2016年08月 追記 ココカラ)

会社のブログでMarionetteを使ってGebを実行する方法を紹介しました。

Astamuse Lab - Web画面自動テストフレームワーク「Geb」の紹介

(2016年08月 追記 ココマデ)

どーも、えいやです。

今日はゴッドイーターバーストではなく、Web画面テストの話です。

システムテストとかインテグレーションテストあたりを自動化したい人向け。

Gebを使ってWebテストを書くことで、Jenkinsさんあたりが画面がブラウザでちゃんと表示できるかとか色々勝手に見てくれて超便利なんでやります。

テストフレームワークですが、なんならWebスクレイピングにも使えます。通常Webスクレイピングにあまり向いてないクライアントスクリプトバリバリなサイトに対して有効です。


Gebってなんね

Gebとは( http://www.gebish.org )、GroovyでWebテスト自動化ツールのSeleniumを実行するためのフレームワークです。


Seleniumってなにか

Seleniumとは( http://docs.seleniumhq.org )、使うとブラウザでWeb画面を叩くテストを自動的にやってくれるツールです。

ボタンとか指定して押した時のブラウザの動作を、JavaScriptの実行だのを含めてテストできます。

インテグレーションテストとか自動化したい人には便利です。


Gebでのテストってどうやるの?

プロジェクトの正式なテストコードとして管理したい場合とかは、一旦置いておいて、まずはサクッと動かす方法を紹介します。

GebはGroovyで書きます。

検証とかに使うので、Groovyコンソールが使えるようにしておいてください。

使用するブラウザはFireFoxが一番素直なので、FireFoxを使いますが、他にもChromeやIEを使うこともできます。

ただし、FireFox以外のブラウザを使用する場合は別途ブラウザの設定や、ブラウザを操作するためのドライバを有効にするためのexeファイルが必要だったりします。

すごく大変というわけではありませんが、この記事では一貫してFireFoxで説明しますね。

最初のコードは次のコードです。Groovyコンソールで実行できます。


FirstTest.groovy

@Grab('org.gebish:geb-core') // 注意:geb-core最新版はorg.codehaus.gebのグループではない

@Grab('org.seleniumhq.selenium:selenium-java')

import geb.Browser

Browser.drive {
go 'https://google.co.jp'
$('input[type=text]').value('groovy')
assert $('input[type=text]').value() == 'groovy'
}


上記のコードは、Googleを開いて、inputタグでtype属性の値がtextのもののvalue属性をgroovyに設定して、それが反映されているかを確認するというテストです。

go 'https://google.co.jp'の部分は読んだそのまま、ブラウザに対して、そのURLに行けという命令です。

以降は、Web画面の操作と、その操作によって変更されたことの確認です。

Web画面中の要素をオブジェクトとして取得するためのコードの記法はjQueryに似た記法になっています。

Web画面、すなわちドキュメントをオブジェクトとして捉えたものをDOMと、Web画面の場合HTMLによってツリー構造となっているため、Web画面のDOMの構造そしてツリーとしての集合をDOMツリーとか言います。DOMツリーのノードはDOM要素とか呼んだりします。

DOMツリーから特定の要素を抽出するもの、上記ではinput[type=text]の部分のことをセレクタと言います。

このセレクタはCSS3に定義されているセレクタと、ほとんど同じものを使うことできます。

Groovyコンソールは記述したスクリプトをCtrl+rで実行できます。

(コンソール画面の下部にログや結果がでます。この欄をクリアするときは、Ctrl+wです。)

スクリプトを実行した時、FireFoxが立ち上がってGoogleにアクセスしに行くことを確認して下さい。

(※ 立ち上がったブラウザ画面は閉じないほうが良いです。

閉じてしまった場合は、次の実行の前にGroovyコンソールのScript->Clear Script Contextとする必要があります。)

上記のコードはGoogleにアクセスに行くので、assertがGreenかどうかはGoogleに依存してしまうので、結果がRedの場合、コードを適宜書き換えて実行してください。

とりあえずは、このようにGebと使用するとブラウザの動作を自動化することができます。


テストとしての体裁を整える

テストとして管理するために、最初に書いたコードをテストっぽい記述にします。

GebとJUnitやeasybといったテストフレームワークを連携させるためのライブラリはいくつかあります。

ここではBDDの体裁を使えるのとGroovyっぽいのでgeb-spockを使用します。SpockはRubyのRSpecのような何かです。

Spockの記法はBDDの体裁ですが、JUnitのクラスを生成してくれます。

mvnやGradleでテストを実行させる場合もJUnitとして実行すれば普通にテストが実行できます。

geb-spockを使用して最初のコードを書き直すと次のようになります。


SecondTest.groovy

@Grab('org.gebish:geb-core')

@Grab('org.gebish:geb-spock')
@Grab('org.seleniumhq.selenium:selenium-java')
import org.openqa.selenium.firefox.FirefoxDriver
import geb.spock.GebSpec
class SecondTest extends GebSpec {

def driver = { new FirefoxDriver() }

def 'a search word can be input'(){
setup:
go 'https://google.co.jp'

when:
$('input[type=text]').value(inputWord)

then:
$('input[type=text]').value() == expectWord

where:
inputWord | expectWord
'groovy' | 'groovy'
}
}


行数は増えますが、前提、条件、検査、検査対象を分離して記述できるので、テストシナリオが把握しやすくなったと思います。


ページの定義を分離する

Gebでは、Pageクラスのサブクラスを定義することによって、あるURLを持つページがどのようなものであるのかを、抽象的に定義することができます。

ページの定義を分離しておくことにより、テストコードの内容は、そのテスト対象のページがどんな構造のドキュメントで表現されているかに依らないものになります。

やってみましょう。

この方法を取る場合は、ページクラスを定義するファイルを分ける必要があります。

適当なパッケージを宣言して、パッケージディレクトリを作り、そこにクラスを定義するファイルを置きます。

クラスを定義するファイルは下記の例では、

${workdir}/jp/eiya/aya/geb/practice/GoogleTopPage.groovy

です。


GoogleTopPage.groovy

package jp.eiya.aya.geb.practice

@Grab('org.gebish:geb-core')
import geb.Page

class GoogleTopPage extends Page{
static url = 'https://google.co.jp'

static content = {
searchBox {
$('input[type=text]')
}
}
}


上記は、GoogleTopPageが、URLがhttps://google.co.jpであり、input[type=text]によって選択されるDOM要素searchBoxをContentとして持っている、ことを定義しています。

GoogleTopPageにより、次のようにテストを書き換えることができます。

ただし、そのままではGoogleTopPageを解決できないので、${workdir}にクラスパスを通しておきましょう。

クラスパスの通し方は、Groovyコンソールのメニューから

Script->Add Directory to ClassPath

で追加します。


ThirdTest.groovy

@Grab('org.gebish:geb-core')

@Grab('org.gebish:geb-spock')
@Grab('org.seleniumhq.selenium:selenium-java')
import org.openqa.selenium.firefox.FirefoxDriver
import geb.spock.GebSpec
import jp.eiya.aya.geb.practice.GoogleTopPage

class ThirdTest extends GebSpec {

def driver = { new FirefoxDriver() }

def 'a search word can be input'(){
setup:
to GoogleTopPage

when:
searchBox.value(inputWord)

then:
searchBox.value() == expectWord

where:
inputWord | expectWord
'groovy' | 'groovy'
}
}


上記を見てみると、このテストは、GoogleTopPageがどんなURLか、また、searchBoxがどのような要素かに依らず、

GoogleTopPageに行き、searchBoxに入力した値がそのまま入力されていること

をテストするコードになりました。

Pageの定義は、大体jQueryが分かれば書くことができます。そのため、ページ構造の変更に際してこれらの定義をフロントエンドのエンジニアや、あるいはデザイナに任せることができるでしょう。(やってくれればね)


定義したページが本当にそのページなのか確かめる

上記でGoogleTopPageを定義しましたが、このGoogleTopPageのURLをhttps://www.bing.comとして、それを使ったThirdTestはGoogleのトップページでなくとも成功します。

そこで、テストの前提条件であるはずの、その定義がきちんと定義対象と合っているかどうか、をテストすることにします。

GoogleTopPage.groovyを次のように書き換えて、CorrectedGoogleTopPage.groovyを作りましょう。


CorrectedGoogleTopPage.groovy

package jp.eiya.aya.geb.practice

@Grab('org.gebish:geb-core')
import geb.Page

class CorrectedGoogleTopPage extends Page{
static url = 'https://www.bing.com' // fail

static at = {
title == 'Google'
searchBox.value() == ''
}

static content = {
searchBox {
$('input[type=text]')
}
}
}


static atに定義されている内容は、このPageの定義を確かめるためのテストコードになっています。

内容は、ページのタイトルが、Googleで、searchBoxの初期値が空欄であること、です。

この定義を使ってテストするには、先ほどのテストコードのimport行を次のように書き換えます。

import jp.eiya.aya.geb.practice.CorrectedGoogleTopPage as GoogleTopPage

Page定義でURLがBingのものなので、このテストは、次のように失敗するはずです。

JUnit 4 Runner, Tests: 1, Failures: 1, Time: 1115

Test Failure: a search word can be input(jp.eiya.aya.geb.practice.ThirdTest)
Assertion failed:

title == 'Google'
| |
Bing false

次に、

static url = 'https://www.bing.com'

static url = 'https://google.co.jp'と書き換えてください。

ちゃんと成功するはずです。

このようにPage定義内でatを定義することによって、ページ定義自身の確からしさが担保されます。

なお、atに記述する内容は、大抵の場合、ページの初期状態を確認するテストがよいでしょう。

テキストボックスに何かを入力したり、ボタンをクリックしたりなどのイベントは、BDDの体裁で記述できるgeb-spockによるテストが向いています。

また、atのみをテストするだけでも画面が死んでないかとか、本来はデータが表示されるべきところが表示されていないか(あるいは仮置きのテンプレートがそのまま出ていないか)どうかとかがわかります。


まとめ

今回は、Gebを用いてテストを書く場合の基本的な方法を紹介しました。

なんか便利なのでやっておきましょう。

次回以降があれば(僕はやるって言ってやらない人なので)、他のブラウザでのテストやJenkinsで動かす方法、大量のPage定義を一括でテストする方法などを紹介できると思います。

以上