Gaugeで自動テストする

## 概要
Java Advent Calendarの3日目担当 @SatohJohn です。
今回はGaugeをつかって自動テストを記述するための設定などを書かせてもらいます。
どうも、[JJUGで発表したもの](https://docs.google.com/presentation/d/1G5fLHaX5-ECgplPel7rwklJ-gUAafqz-lD1-OHpLKy0/edit?usp=sharing) と若干同じネタで恐縮ではありますが、細かい部分とか一応文章にしてまとめておこうと思います。

## 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の情報が記述されています。

```json: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は以下のようになります。

```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]

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

```java: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ファイルから呼び出す

```markdown: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ファイルに記述します。

```markdown:test.cpt
# テスト
* "http://www.java-users.jp/ccc2017fall/"を開く
```
上記のように記述した場合、以下のようにtest.specを書き換えることができます

```markdown: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