Cucumber を Groovy で実行 - 単体実行

はじめに

cucumber-jvm は BDD ツール Cucumber の Java 実装版です。

様々な JVM 言語 (clojure、Groovy、ScalaJavaScript 等) を使ってステップ定義を実装できるので、今回は Groovy でステップ定義を実装、単体実行してみる事にします。

とりあえず Web アプリで試してみたかったので、今回は Gitblit に対して Selenium を使ったステップ定義を実行してみます。

Gitblit は Java で実装された Git リポジトリの Web 管理ツール (github みたいなもの) で Apache Wicket や Bootstrap 等が使われています。

使用したソースは http://github.com/fits/try_samples/tree/master/blog/20130720/

フィーチャ

今回は動かす事が目的なので、フィーチャの内容は適当に作成しました。(文字コードUTF-8

日本語で記載するため言語ヘッダーに ja を指定しています。

login.feature
# language: ja
機能: ログイン

  シナリオ: ログイン成功
    前提 Topへアクセス
    もし "username""admin" を入力
    かつ "password""admin" を入力
    かつ "ログイン" ボタンをクリックする
    ならば ログイン済みとなる
  ・・・
repository.feature
# language: ja
@current
機能: リポジトリ操作

  背景:
    前提 ログイン済み

  シナリオ: リポジトリ表示
    もし リポジトリをクリック
    ならば リポジトリページを表示

言語ヘッダーに ja を指定するとキーワード("機能:" や "シナリオ:" 等)に日本語が使えるようになります。

ただし、デフォルトの en であってもキーワードが英語になるだけで、説明文に日本語を使う事は可能です。

en の例
# language: en
Feature: ログイン

  Scenario: ログイン成功
    Given Topへアクセス
  ・・・

ステップ定義

上記のフィーチャに則ったステップ定義を Groovy で実装してみます。

Groovy でステップ定義を実装するには下記 2行を記載して、ステップ定義用メソッドを呼び出せるようにします。 (Java でステップ定義を実装する場合はアノテーションを使います)

this.metaClass.mixin(cucumber.api.groovy.Hooks)
this.metaClass.mixin(cucumber.api.groovy.EN)

ここで、cucumber.api.groovy.EN の代わりに cucumber.api.groovy.JA を使えば下記のように日本語メソッドを使えるのですが、なんとなく分かり難いような気がしたので今回は cucumber.api.groovy.EN を使いました。 (ステップ定義のメソッドをフィーチャの言語ヘッダーと合わせる必要は特にありません。)

日本語メソッドを使ったステップ定義例
this.metaClass.mixin(cucumber.api.groovy.Hooks)
this.metaClass.mixin(cucumber.api.groovy.JA)

もし(~/"(.*)""(.*)" を入力/) { name, value ->
    tester.byName(name).sendKeys(value)
}

ならば(~'ログイン済みとなる') { ->
    assert tester.byXpath("//a[. = 'ログアウト']") != null
}

まずは、Before・After フックと共通的なステップ定義をまとめたファイルを作成してみました。Selenium API はそのままでは使い難いので、Gitblit の検証用にラッパークラス WebTesterDriver も定義しています。

Web ブラウザには Firefox を使うようにしました。

common.groovy
package sample

this.metaClass.mixin(cucumber.api.groovy.Hooks)
this.metaClass.mixin(cucumber.api.groovy.EN)

// Gitblit の Top ページ URL
TOP_URL = 'https://localhost:8443/'

@Grab('org.seleniumhq.selenium:selenium-java:2.33.0')
import org.openqa.selenium.*

class WebTesterDriver {
    private driver

    WebTesterDriver(driver) {
        this.driver = driver
    }

    def button(String caption) {
        byXpath("//button[text() = '${caption}']")
    }

    def link(String caption) {
        byXpath("//a[contains(., '${caption}')]")
    }

    def isError() {
        byXpath("//span[@class = 'feedbackPanelERROR']") != null
    }

    def to(String url) {
        driver.get(url)
    }
    ・・・
}

Before() {
    // Firefox の使用
    drv = new org.openqa.selenium.firefox.FirefoxDriver()
    tester = new WebTesterDriver(drv)
}

After() {
    drv.quit()
}

Given(~'Topへアクセス') { ->
    tester.to(TOP_URL)
}

Given(~'ログイン済み') { ->
    tester.to(TOP_URL)
    tester.byName('username').sendKeys('admin')
    tester.byName('password').sendKeys('admin')
    tester.button('ログイン').click()
}

When(~/"(.*)" ボタンをクリックする/) { caption ->
    tester.button(caption).click()
}

Then(~'エラー表示') { ->
    assert tester.isError()
}

次に、各フィーチャファイルに対応したステップ定義を実装しました。 XPath を多用して HTML の内容に依存しすぎているので検証内容としてはかなり微妙な感じがしますが、今回はこれで良しとします。

login.groovy
package sample

this.metaClass.mixin(cucumber.api.groovy.Hooks)
this.metaClass.mixin(cucumber.api.groovy.EN)

When(~/"(.*)""(.*)" を入力/) { name, value ->
    tester.byName(name).sendKeys(value)
}

Then(~'ログイン済みとなる') { ->
    assert tester.byXpath("//a[. = 'ログアウト']") != null
}
repository.groovy
package sample

this.metaClass.mixin(cucumber.api.groovy.Hooks)
this.metaClass.mixin(cucumber.api.groovy.EN)

When(~'リポジトリをクリック') { ->
    tester.link('リポジトリ').click()
}

Then(~'リポジトリページを表示') { ->
    assert tester.byXpath('//table[@class="repositories"]') != null
}

なお、~/・・・/ と ~'・・・' を使い分けていますが、特に深い意味はありません。 ステップ定義用のメソッドは下記のような実装ですので、java.util.regex.Pattern を第1引数に渡すだけです。

EN クラスの内容
public class EN {
    ・・・
    public static void Given(Pattern regexp, Closure body) throws Throwable {
        GroovyBackend.instance.addStepDefinition(regexp, 0, body);
    }

    public static void Given(Pattern regexp, int timeoutMillis, Closure body) throws Throwable {
        GroovyBackend.instance.addStepDefinition(regexp, timeoutMillis, body);
    }
    ・・・
}

単体実行

それでは、実行してみます。 Maven 等を使って実行することも可能ですが、今回は単体で実行してみることにします。

cucumber-jvm を単体実行するには依存モジュールを CLASSPATH に設定して cucumber.api.cli.Main クラスの main メソッドを呼び出すだけなのですが( java -jar cucumber-core-1.1.3.jar )、CLASSPATH の設定が面倒そうだったので今回は Groovy で実行スクリプトを書いてみました。

cucumber-groovy を @Grab 指定するだけなので非常に簡単です。

run_cucumber.groovy (cucumber-jvm 単体実行スクリプト)
@Grab('info.cukes:cucumber-groovy:1.1.3')
import cucumber.api.cli.Main

def loader = Thread.currentThread().getContextClassLoader()

Main.run(args, loader)
// 以下でも可
// Main.main(args)

実行

まず Gitblit を起動しておきます。(実行方法は後述)

次に groovy コマンドを使って run_cucumber.groovy を実行します。

その際に、ステップ定義の配置ディレクトリ(-g もしくは --glue で指定)とフィーチャの配置ディレクトリを指定します。(ステップ定義を UTF-8 で実装しているので Groovy の実行オプション -c UTF-8 も指定しています)

実行例1
> groovy -c UTF-8 run_cucumber.groovy -g scripts features

これで Firefox がシナリオ毎に立ち上がり、テストが実施されると思います。

Windows の cmd.exe で実行する場合、カラー出力に対応していないため -m (monochrome)オプションを指定して実行しないと正常に出力されません。(下記の "." は passed を表しています)

実行例2 (モノクロ出力)
> groovy -c UTF-8 run_cucumber.groovy -g scripts -m features

.............

なお、ファイル構成は下記のようにしています。

  • features ディレクトリ
    • login.feature
    • repository.feature
  • scripts ディレクトリ
    • common.groovy
    • login.groovy
    • repository.groovy
  • run_cucumber.groovy

タグやフォーマットを指定して実行

特定のタグを付けたフィーチャやシナリオを実行したり、出力結果のフォーマットを指定したい場合は下記のようなオプションを使用します。

  • -t もしくは --tags で特定のタグを対象にする
  • -f もしくは --format で出力フォーマットを指定

例えば、@current タグを付けたフィーチャ (今回は "リポジトリ操作" に該当) を実行し、結果を JUnit のフォーマットで reports.xml へ出力したい場合、下記のようなオプションで実行します。

実行例3 (@current のみ実行し、処理結果を JUnit 形式で reports.xml へ出力)
> groovy -c UTF-8 run_cucumber.groovy -g scripts -t @current -f junit:reports.xml features

出力ファイルは下記のような内容になっています。

reports.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<testsuite failures="0" tests="1">
<testcase classname="リポジトリ操作" name="リポジトリ表示" time="15.614886">
<system-out><![CDATA[前提ログイン済み..................................................................passed
もしリポジトリをクリック................................................................passed
ならばリポジトリページを表示...............................................................passed
]]></system-out>
</testcase>
</testsuite>

Gitblit の起動方法

最後に Gitblit の起動方法ですが、Gitblit GO (スタンドアロン版) を使えば簡単です。

http://gitblit.com/ の 「Download Gitblit GO」 をクリックし、適当なディレクトリへ解凍します。

Windows 上では java コマンドを実行できる環境で gitblit.cmd を実行するだけで起動します。(Linux なら gitblit start で起動すると思います)

Gitblit 起動例 (Windows の場合)
> gitblit

INFO  ***********************************************************
INFO              _____  _  _    _      _  _  _
INFO             |  __ \(_)| |  | |    | |(_)| |
INFO             | |  \/ _ | |_ | |__  | | _ | |_
INFO             | | __ | || __|| '_ \ | || || __|
INFO             | |_\ \| || |_ | |_) || || || |_
INFO              \____/|_| \__||_.__/ |_||_| \__|
INFO                        Gitblit v1.3.0
INFO
INFO  ***********************************************************
・・・
INFO  Started SslSelectChannelConnector@localhost:8443
・・・

停止は gitblit-stop.cmd を使います。(Ctrl + c でも可)

Gitblit 停止例 (Windows の場合)
> gitblit-stop

Sending Shutdown Request to Gitblit