Drupal 向け Behat の拡張その1 複数ステップの実行とカスタムステップの作成（前編）

前回、[Drupal 8 に Drupal 向け Behat のスタンドアロンでの導入とシンプルなテストシナリオ](http://qiita.com/tom_konda/items/f9a22f55f458bb954b1f)という記事を書きました。その記事では、最後に簡単なテストシナリオを作成しましたが常に1つのステップと事前に定義されたステップだけでシナリオを組み立てることはかなり困難です。今回は複数ステップの実行と上記の記事でエラーメッセージの際に少し触れたカスタムステップの作成について説明していきます。

## 複数ステップの実行

前回作成したテストシナリオでは、前提条件、行う動作、期待する結果で実行するステップが1つずつでした。しかし、冒頭でも記述しましたが実際にテストシナリオを作成する際にステップが1つで済むことは少ないです。

今回の説明で使用するフィーチャーファイルを作成します。作成するファイル名は test2.feature にします。

```bash
cd /path/to/behat/behat-tests # Behat 動作ディレクトリ
touch features/test2.feature
```

作成するテストシナリオは下記になります。

<dt>Feature 名</dt>
<dd>テストフィーチャー2</dd>
<dt>シナリオ名</dt>
<dd>複数ステップの実行</dd>
<dt>前提条件</dt>
<dd>ユーザはログインしていない</dd>
<dd>トップページにアクセス</dd>
<dt>行う動作</dt>
<dd>/hogehoge にアクセス</dd>
<dd>1つ前のページに戻る</dd>
<dt>期待する結果</dt>
<dd>ステップ実行時の URL を表示する</dd>
<dd>HTTP ステータスコードは 404 ではない</dd>

今回は、前提条件、行う動作、期待する結果のそれぞれでステップを2つずつ記述しています。これらのステップを定義済みのステップから探し出すと、下記のようになります。

```gherkin:test2.feature
# Feature 名
Feature: テストフィーチャー2
  # Scenario 名
  Scenario: 複数ステップの実行
    # シナリオの中身
    Given I am not logged in
    Given I am at ""
    When I go to "hogehoge"
    When I move backward one page
    Then print current URL
    Then the response status code should not be 404
```

この内容で、Behat を実行すると下記のように問題なく実行できます。

```bash
Feature: テストフィーチャー2

  Scenario: 複数ステップの実行                               # features/test2.feature:4
    Given I am not logged in                        # Drupal\DrupalExtension\Context\DrupalContext::assertAnonymousUser()
    Given I am at ""                                # Drupal\DrupalExtension\Context\MinkContext::assertAtPath()
    When I go to "hogehoge"                         # Drupal\DrupalExtension\Context\MinkContext::visit()
    When I move backward one page                   # Drupal\DrupalExtension\Context\MinkContext::back()
    Then print current URL                          # Drupal\DrupalExtension\Context\MinkContext::printCurrentUrl()
      │ http://localhost:8888/
    Then the response status code should not be 404 # Drupal\DrupalExtension\Context\MinkContext::assertResponseStatusIsNot
()
```

しかし、全てステップの前に `Given`、`When`、`Then` が付いているためどこからどこが前提条件を指すのかが分かりづらいです。Behat では、`Given`、`When`、`Then` が複数存在している時、それらの2つ目以降は `And` や `But` に置き換えることが出来ます。それらを置き換えると下記のようになります。

```gherkin:test2.feature
# Feature 名
Feature: テストフィーチャー2
  # Scenario 名
  Scenario: 複数ステップの実行
    # シナリオの中身
    Given I am not logged in
    And I am at ""
    When I go to "hogehoge"
    And I move backward one page
    Then print current URL
    But the response status code should not be 404
```

置き換えた後に Behat を実行すると、置き換える前と同様な結果が出力されます。

```bash
Feature: テストフィーチャー2

  Scenario: 複数ステップの実行                              # features/test2.feature:4
    Given I am not logged in                       # Drupal\DrupalExtension\Context\DrupalContext::assertAnonymousUser()
    And I am at ""                                 # Drupal\DrupalExtension\Context\MinkContext::assertAtPath()
    When I go to "hogehoge"                        # Drupal\DrupalExtension\Context\MinkContext::visit()
    And I move backward one page                   # Drupal\DrupalExtension\Context\MinkContext::back()
    Then print current URL                         # Drupal\DrupalExtension\Context\MinkContext::printCurrentUrl()
      │ http://localhost:8888/
    But the response status code should not be 404 # Drupal\DrupalExtension\Context\MinkContext::assertResponseStatusIsNot(
)
```

置き換えたことで、シナリオ内で `Given`、`When`、`Then` がそれぞれ1回のみ出てくるようになったため、どこからどこが前提条件なのかが分かりやすくなりました。
[Behat の公式ドキュメント](http://docs.behat.org/en/v2.5/guides/1.gherkin.html#and-but)のように `And` や `But` の部分でインデントをつけることで読みやすさを改善することが出来ます。

## パラメータがないカスタムステップの作成

カスタムステップを作成するためのテストシナリオを用意します。
今回のテストシナリオでは、test3.feature というファイル名にします。

```bash
cd /path/to/behat/behat-tests # Behat 動作ディレクトリ
touch features/test3.feature
```

作成するテストシナリオは下記になります。

<dt>Feature 名</dt>
<dd>テストフィーチャー3</dd>
<dt>シナリオ名</dt>
<dd>カスタムステップの作成</dd>
<dt>前提条件</dt>
<dd>ユーザはログインしていない</dd>
<dt>行う動作</dt>
<dd>&lt;front&gt; にアクセス</dd>
<dt>期待する結果</dt>
<dd>ステップの実行した現在時間を表示する</dd>

前提条件と行う動作に関しては、前回作成したテストシナリオとほとんど変わらないため定義済みのステップが存在します。しかし、“ステップの実行時間を表示する”は定義されていないため新たに定義を作成する必要があります。期待する結果への定義なので、最初の部分は `Then` になります。動作は現在時刻を表示するので、

```gherkin
Then print current date
```

と記述できます。
上記のテストシナリオは下記のようになります。

```gherkin:test3.feature
# Feature 名
Feature: テストフィーチャー3
  # Scenario 名
  
  Scenario: カスタムステップの作成
    # シナリオの中身
    Given I am not logged in
    When I go to "<front>"
    Then print current date
```

もちろん、このシナリオを実行しても定義されていないため下記のようにエラーが出てきます。

```bash
--- FeatureContext has missing steps. Define them with these snippets:

    /**
     * @Then print current date
     */
    public function printCurrentDate()
    {
        throw new PendingException();
    }
```

エラーメッセージに作成する関数のヒントが書かれているため、これを `/path/to/behat/behat-tests/features/bootstrap/FeatureContext.php` に追記します。追記する際は、コメントに見える

```php
    /**
     * @Then print current date
     */
```

の部分も含めて追記します。（この部分がないとステップが未定義のままとして扱われます）
追記内容のままだと、`throw new PendingException();` で例外を投げてしまい何もできないのでコードを書き換えていきます。

コードを書き換える前にすでに定義されている、ステップ実行時の URL を表示するステップ `Then print current URL` の[ソースコード](https://github.com/Behat/MinkExtension/blob/160d4b77095dee2b4379e9a312977e8dcdf01b00/src/Behat/MinkExtension/Context/MinkContext.php#L497)を見てみます。（Composer でインストールされたファイルならパスは、`/path/to/behat/vendor/behat/mink-extension/src/Behat/MinkExtension/Context/MinkContext.php` です）

```php:MinkContext.php
    /**
     * Prints current URL to console.
     * Example: Then print current URL
     * Example: And print current URL
     *
     * @Then /^print current URL$/
     */
    public function printCurrentUrl()
    {
        echo $this->getSession()->getCurrentUrl();
    }
```

該当部分を抜粋すると上記になります。Behat のシナリオでも `echo` を使えば通常の PHP プログラムと同じく結果を出力できることが分かります。

`echo` を使えばよいことが分かったので、コードを書き換えます。

```php:FeatureContext.php
  /**
   * @Then print current date
   */
  public function printCurrentDate()
  {
    echo (new DateTime())->format('Y/m/d H:i:s');
  }
```

書き換えた結果は上記のようになり、このテストシナリオを実行すると下記のようになります。

```bash
Feature: テストフィーチャー3

  Scenario: カスタムステップの作成      # features/test3.feature:5
    Given I am not logged in # Drupal\DrupalExtension\Context\DrupalContext::assertAnonymousUser()
    When I go to "<front>"   # Drupal\DrupalExtension\Context\MinkContext::visit()
    Then print current date  # FeatureContext::printCurrentDate()
      │ 2016/12/08 12:35:02

2 scenarios (2 passed)
6 steps (6 passed)
0m0.52s (11.18Mb)
```

`Then print current date` の次の行にステップの実行した時刻が表示されました。これで、引数がない時のカスタムステップが作成できました。

## まとめ

今回の記事をまとめると以下のようになります。

- シナリオ内のステップで `Given`、`When`、`Then` が複数回使えることを示しました
- `Given`、`When`、`Then` を複数回使用する場合は `And` や `But` でも記述ができる
  - `And` や `But` で置き換えた方が、前提条件や行う動作、期待する結果の範囲が分かりやすくなる
- パラメータがないカスタムステップを作成した
  - PHP での実装の際にステップの記述が必要になる
  - パラメータが有るパターンは次回になります

---
### Behat on Drupal 8 の記事一覧

| 回 | タイトル |
|:-:|:-:|
|  第1回 |  [Drupal 8 に Drupal 向け Behat のスタンドアロンでの導入とシンプルなテストシナリオ](http://qiita.com/tom_konda/items/f9a22f55f458bb954b1f) |
|  第2回 |  **Drupal 向け Behat の拡張その1 複数ステップの実行とカスタムステップの作成（前編）** |
| 第3回 |[Drupal 向け Behat の拡張その2 カスタムステップの作成（後編）](http://qiita.com/tom_konda/items/d54d4b66cdd4c310703e)|