GaugeのJava実装を試してみる

前回の続き。

Taikoを使ったJavaScriptによるテスト実装でもろもろ躓いたので、Javaによるテスト実装を試してみる。

環境

Gauge v1.1.5、AdoptOpenJDK v11.0.9.1。

Selenium WebDriverはChocolateyでインストールしたChrome Driverを使用。

JDKのバージョンについて

環境にも書いたが、公式の記述によると、JDK11以上が必要とのこと。

今回はChocolatey の cinst adoptopenjdk11 でインストールした、AdoptOpenJDKを使用。

プロジェクトの作成

前回記事参照java_maven_selenium を選択した。

当初はVSCodeでしか実装できないのかと思ったが、Eclipseプロジェクト用の .classpath.project も生成されており、またテスト実行も mvn test で行えるため、プロジェクト生成をVSCodeで行ってしまえば、任意の開発環境で実装可能と思われる。

(公式では紹介されていないとはいえ、GaugeプラグインのあるIntellijはともかく、Eclipseで実装するのは辛そうだが)

pom.xmlの修正

作成直後では、 pom.xml文字コード指定が行われていない。

mvn test でテストの実行ができるが、UTF-8spec ファイル記述し、そこに日本語などが含まれると、MS932で読み込もうとしてエラーになるため、 project.build.sourceEncoding を追加しておく。

<project ...>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

また、デフォルトではアサーションライブラリとしてAssertjが依存性に含まれているため、適宜変更。JUnit4などのテストフレームワークは依存性に含まれない。

Java実装

公式のWrite Gauge specifications#Step implementationsに、テスト実装の方法が記載されている。

WebDriver用のヘルパークラス

java_maven_selenium でプロジェクトを作成した場合、 driver パッケージに Driver および DriverFactory クラスが生成されている。

Driver.webDriver staticフィールドに、 DriverFactory にて、 System.getenv("BROWSER") に応じた WebDriver インスタンスが設定される。

  1. IE ならば InternetExplorerDriver
  2. FIREFOX ならば FirefoxDriver
  3. それ以外(未指定含む)は ChromeDriver
    • ChromeDriver の場合、 System.getenv("HEADLESS")Y または y ならばヘッドレスモードで実行

異なるWebDriverを利用する場合、DriverFactoryを変更すればいい。

実行フック

JUnit4などと同様、任意の public void メソッドにアノテーションを付与することで、ライフサイクルごとに前処理/後処理が可能。「Test execution hooks」と呼ばれている。

ライフサイクル 前処理 後処理
スイート @BeforeSuite @AfterSuite
スペック @BeforeSpec @AfterSpec
シナリオ @BeforeScenario @AfterScenario
ステップ @BeforeStep @AfterStep

例えば、先の driver.Driver でのWebDriver生成処理は @BeforeSuite で、 WebDriver#quit() の実行は @AfterSuite の付いたメソッドで実行されている。

これらのアノテーションが付与されたメソッドでは、引数として ExecutionContext を指定しておくことで、実行時のコンテキストを取得できる模様。

また、 スイート以外のアノテーションでは、 tags パラメーターを指定することで、特定のタグが付与されている場合のみ実行といったフィルタリングが可能。

デフォルトでは tags に指定したタグのAND条件だが、tagAggregation = Operator.OR を指定しておくことで、OR条件に変更可能。

データの保存

ライフサイクルに応じたデータの保存用に、いくつかのデータストアクラスが用意されている。各ライフサイクルが終了すると、設定されているデータが破棄される。

ライフサイクル データストア
スイート SuiteStore
スペック SpecDataStore
シナリオ ScenarioDataStore

それぞれstaticメソッドとして put(Object key, Object value) , get(Object key) , remove(Object key) などが用意されており、ほぼ Map<Object, Object> として使える。

(というか、中身は ThreadLocal<ConcurrentHashMap<Object, Object>> )

ステップとメソッドの紐づけ

com.thoughtworks.gauge.Step アノテーションに、 .spec で記述したステップの文字列を記述する。

TaikoによるJavaScript開発時と同様、VSCode上でテスト実装のない .spec のステップや、パラメータを使用しているステップと紐づけたメソッドに引数がない/多い場合など、きちんとエラーにしてくれる。

レポートにスクリーンショットを追加

テスト失敗時には、自動的にスクリーンショットが取られ、レポートに出力される。

任意のタイミングでスクリーンショットを取り、レポートに出力したい場合、 com.thoughtworks.gauge.Gauge#captureScreenshot() を実行すると、そのメソッドに対応するステップに、スクリーンショットが追加される。

試しにスクリーンショットを取ってみたが、デフォルトではブラウザだけでなく、デスクトップ画面全体が映っていた。WebDriverのウィンドウサイズを全画面化するなど、ちょっと工夫がいるかもしれない。

レポートにカスタムメッセージを追加

com.thoughtworks.gauge.Gauge#.writeMessage(String format, String... args) を呼び出すと、そのメソッドに対応するステップに、メッセージが追加される。

文字列は String#format(String format, Object... args) でフォーマットされるので、 %sプレースホルダとして利用可能。

実装例

前回のリベンジ。

spec/search.spec に保存していたMarkdownを、以下のように変更。

# 検索エンジンのタイトル確認

Tags: example, search

   |url                    |title |
   |-----------------------|------|
   |https://www.google.com/|Google|
   |https://www.bing.com/  |Bing  |
   |https://yahoo.com/     |Yahoo |

## 検索エンジンを開いて、タイトルを確認する

* URL <url> を開く
* タイトルが <title> となることを確認

続いて、以下のクラスを src/test/java/SearchSpec.java に保存。

import com.thoughtworks.gauge.Gauge;
import com.thoughtworks.gauge.Step;
import driver.Driver;

import static org.assertj.core.api.Assertions.assertThat;

public class SearchSpec {

    @Step("URL <url> を開く")
    public void openSearchEngine(String url) {
        Driver.webDriver.get(url);
        Gauge.captureScreenshot();
    }

    @Step("タイトルが <title> となることを確認")
    public void checkTitle(String expected) {
        String actual = Driver.webDriver.getTitle();
        Gauge.writeMessage("search engine title: %s", actual);
        assertThat(actual).isEqualTo(expected);
    }
}

実行してみると上手くいった。また、 .spec.java のファイル名が別々でも問題なく動いたり、 specs ディレクトリにサブディレクトリを作ってそこに search.spec を移動したり、 SearchSpec.java を適当なパッケージ配下に移動しても問題なく実行できた。

Taikoで試したときの、複数の実装ファイルがあるとエラーになるのは何だったんだ...

振り返り

JUnitこそ使っていないものの、それに寄せて仕様が決められている印象で、特に詰まることなく実装出来た。

src/main 配下にSelenideなどでPage Object Patternに乗っ取ったクラスを記述し、テスト実行はGaugeで、とかできると捗る気がする。