概要

Java Advent Calendarの3日目担当 @SatohJohn です。
今回はGaugeをつかって自動テストを記述するための設定などを書かせてもらいます。
どうも、JJUGで発表したもの と若干同じネタで恐縮ではありますが、細かい部分とか一応文章にしてまとめておこうと思います。

Gaugeとは

https://getgauge.io/index.html
自動テストをマークダウンで記述することができるツールです。

メリット

  • テストケースをマークダウン(日本語)で記述することができるため、テスト内容や結果をレビューがしやすい
    • テストの実装が増えると、非エンジニアでも自動テストを書くことができる
  • マークダウン部分を残して、実装を別言語(C#など)にすることができる
  • IntelliJなどのIDEプラグインがある
  • WindowsでもMacでもLinuxでも動かせる

デメリット

  • まだベータ版
    • バージョンを上げると動かないこともある
  • Stepの実装をきちんと構成しないと、同じような実装をしがちになる
    • StepとVerifyでファイルを分けたほうが良さそう

準備

Gaugeをinstallします
https://getgauge.io/get-started.html
macではbrewでinstallすることができます。

構成

mavenプロジェクトを以下のように作成します。(treeコマンド便利!

.
├── driver
│   ├── chromedriver_linux
│   ├── chromedriver_mac
│   └── chromedriver_windows.exe
├── env
│   └── default
│       ├── default.properties
│       └── java.properties
├── manifest.json
├── pom.xml
├── specs
│   └── test.spec
└── src
    └── test
        └── java
            ├── StepImplementation.java
            └── VerifyImplementation.java

driverは今回seleniumでchromeを利用するので入れてます。
manifest.jsonは実装形式と、Gaugeのpluginの情報が記述されています。

manifest.json
{
  "Language": "java",
  "Plugins": [
    "spectacle"
  ]
}

envには設定を置きます。他にもパラメータはありますが、実際に使うのは、こんなものではないかと

default.properties
# 実施結果を置くディレクトリ
gauge_reports_dir = reports
# 実施結果を更新するか
overwrite_reports = true
# 失敗した場合にscreenshotを取るか
screenshot_on_failure = true
# 実施内容のlog
logs_directory = logs
java.properties
# JVMに渡すパラメータ
gauge_jvm_args = -Dselenide.browser=chrome
# 状態の初期化のレベル(後述)
gauge_clear_state_level = scenario

pom.xmlは以下のようになります。

pom.xml
<dependencies>
    <dependency>
        <groupId>com.thoughtworks.gauge</groupId>
        <artifactId>gauge-java</artifactId>
        <version>0.6.5</version>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.codeborne</groupId>
        <artifactId>selenide</artifactId>
        <version>4.8</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <groupId>com.thoughtworks.gauge.maven</groupId>
            <artifactId>gauge-maven-plugin</artifactId>
            <version>1.3.0</version>
            <executions>
                <execution>
                    <phase>test</phase>
                    <configuration>
                        <specsDir>specs</specsDir> <!--実行するspecのディレクトリ-->
                    </configuration>
                    <goals>
                        <goal>execute</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

guavaのバージョンを変更しておいてください。そうしないと、実行前にselenideのライブラリと競合して以下のような例外が出てしまいます。

Failed: Before Suite
Message: java.lang.NoSuchMethodError: com.google.common.util.concurrent.SimpleTimeLimiter.create(Ljava/util/concurrent/ExecutorService;)Lcom/google/common/util/concurrent/SimpleTimeLimiter;

specsがmarkdownを入れているディレクトリです。
testの中のjavaはmarkdownの実装となっています。書き方は以下のようになります。

書き方

基本的には、自動テストを書いていくのは以下の流れになると思います
1. @Stepのついたメソッドを実装する
2. そのメソッドをspecファイルから呼び出す
3. 同じようなStepが出てきたらまとめる

1. @Stepのついたメソッドを実装する

実行する部分の実装は、以下のように適当なクラスを作成し、そのメソッドに@Stepをつけます。
この時specファイルから呼び出される際の名前を決めます。[1]

StepImplementation.java
import com.codeborne.selenide.Selenide;
import com.thoughtworks.gauge.Step;

public class StepImplementation {
    @Step("<url>を開く") //[1]
    public void open(String url/*[2]*/) {
        Selenide.open(url); //[3]
    }
}

は変数を表していますが、引数名と一致していなくても問題はありません。(一致させたほうが見やすいかと)[2]
もし、同じ「"変数"を開く」という名前の実装があった場合、「Duplicate step implementation」ということでテストは実施されません。Stepを複数のクラスに書いていく場合は気をつけましょう。
このテストではSelenideを使って変数のページを開いています。[3]
Verifyに関しても全く同じです。

VerifyImplementation.java
import com.codeborne.selenide.Selenide;
import com.thoughtworks.gauge.Step;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;

public class VerifyImplementation {
    @Step("タイトルが<title>であること")
    public void verifyTitle(String title) {
        String actual = Selenide.title();
        Assert.assertThat(actual, CoreMatchers.is(title));
    }
}

今回の例は、Selenideでページのtitleを取得して、引数と比較しています。

2. そのメソッドをspecファイルから呼び出す

test.spec
テスト [4]
====================

## JJUGのページを開く[5]
[6]
* "http://www.java-users.jp/ccc2017fall/"を開く
* タイトルが"JJUG CCC 2017 Fall"であること

[4]このファイルの全体の名前(specification)になります。実行時に同じファイルのscenarioをまとめて実施できます。
[5]テストケースの名前(scenario)になります。
[6]*は実施するテストの内容(step)になります。先ほどJavaでかいた実装が呼ばれます。"の中身は引数として渡されます。<>でも書けます。
単純な文字だけではなく、ローカルのファイルや、テーブルを渡すことが出来ます。
#などを記述しないでテキストを書くとコメント扱いになります。(ホントは*の行に[6]って書きたかったけど

詳しい記述方法としては https://docs.getgauge.io/longstart.html を見ましょう

3. 同じようなStepが出てきたらまとめる

まとめたものはconceptと言います。conceptは.cptファイルに記述します。

test.cpt
# テスト
* "http://www.java-users.jp/ccc2017fall/"を開く

上記のように記述した場合、以下のようにtest.specを書き換えることができます

test.spec
テスト
====================

## JJUGのページを開く
* テスト
* タイトルが"JJUG CCC 2017 Fall"であること

テスト実行前の処理

@BeforeSuiteで自動テスト実施前にブラウザの設定を変更することができます。

public class BeforeStepImplementation {
    @BeforeSuite
    public void beforeSuite() {
        ChromeOptions options = new ChromeOptions();
        if (SystemUtils.IS_OS_MAC) {
            System.setProperty("webdriver.chrome.driver", "./driver/chromedriver_mac");
        } else if (SystemUtils.IS_OS_WINDOWS) {
            System.setProperty("webdriver.chrome.driver", "./driver/chromedriver_windows.exe");
        } else if (SystemUtils.IS_OS_LINUX) {
            System.setProperty("webdriver.chrome.driver", "./driver/chromedriver_linux");
        }
        options.addArguments("--no-experiments",
                "--disable-translate",
                "--disable-plugins",
                "--disable-extensions",
                "--no-default-browser-check",
                "--clear-token-service",
                "--disable-default-apps",
                "--enable-logging",
                "--headless", "--disable-gpu", // この2つがあるとheadlessの実行になる
                "test-type");
        ChromeDriver chromeDriver = new ChromeDriver(options);
        WebDriverRunner.setWebDriver(chromeDriver);
    }
}

他には@BeforeSpec@BeforeScenario@BeforeStep、また、各タイミングのAfterもありますので各タイミングで初期化をすることが可能です。

自動テストをmavenから実行する

pluginを入れているのでmvn testを実行するだけです。
以下のような感じで結果が出てきます。

# テスト
  ## JJUGのページを開く  ✔ ✔

Specifications: 1 executed      1 passed        0 failed        0 skipped
Scenarios:      1 executed      1 passed        0 failed        0 skipped

Total time taken: 5.535s
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 15.036 s
[INFO] Finished at: 2017-12-02T22:17:39+09:00
[INFO] Final Memory: 11M/159M
[INFO] ------------------------------------------------------------------------

まとめ

自動テストツールGaugeを紹介しました。
マークアップで記述できる点がとてもいいと思っています。また、マークダウンのためレビューもしやすいです。
自動テスト書きましょう!
ちなみに今回のデモはgithubにあげておりますので動かしてみてください!
https://github.com/furyu-john/gauge-sample