前回の続き。
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-8で spec
ファイル記述し、そこに日本語などが含まれると、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
インスタンスが設定される。
IE
ならばInternetExplorerDriver
FIREFOX
ならばFirefoxDriver
- それ以外(未指定含む)は
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で、とかできると捗る気がする。