Laravel5.1でbehatを使ってみる

# なぜbehatを使うのか
PHPカンファレンス福岡でphpspecとbehatのお話を聞いて
behatのほうがちょっと効率よさそうと感じたので先にbethatのほうをやってみることにしました。

↓その時のスライド
phpspecで始めるBDD
http://www.slideshare.net/yuukitakezawa/phpspec-bdd

behatエクステンションの作り方
http://www.slideshare.net/RyoTomidokoro/behat-49897353

ちなみに私は今までほとんどテストコードを書いたことがない… :fearful: 
ずっと「テストコードかかなきゃなぁ」と思いながら従来のアナログな方法でやっていたわけです。ダメ人間ですね。

# 環境
Laravel5時代のhomesteadをベースとしたオレオレVagrant環境を構築済み。
仮想のUbuntu上で作業します。

# 使うもの
ぐぐったらそれようのがあったのでこれを使う
https://github.com/laracasts/Behat-Laravel-Extension

# 設定手順
上記のREADME.mdと動画の手順で進めてみる。

動画
https://laracasts.com/lessons/laravel-5-and-behat-bffs

## composerで一式入れる
laravelのディレクトリに移動して以下を実行
`sudo composer require behat/behat behat/mink behat/mink-extension laracasts/behat-laravel-extension --dev`

## behat.ymlを作る
内容もREADMEのをベースにしてenv_pathに`.env`を指定

`vim behat.yml`

```behat.yml
default:
    extensions:
        Laracasts\Behat:
            env_path: .env
        Behat\MinkExtension:
            default_session: laravel
            laravel: ~
```

Laravelの`.env`をbehat用に用意したいなら、`env_path`はコメントアウトするかファイル指定するといいよ。といってると理解した。

## behat --init
venderの中にbehatがあるのでコマンド実行

```
$ vendor/behat/behat/bin/behat --init
+d features - place your *.feature files here 
+d features/bootstrap - place your context classes here 
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here
```

* featuresディレクトリ作ったよ！featureファイルはここに入れてね！
* その中にbootstrapも作ったよ!
* FeatureContext.phpも雛形作ったよ！あなたの定義はここに書いてね

といった所でしょうか

## featureを書く

Laravel5.1を入れたままの状態なのでテストの内容は
「トップページにアクセスしたらLaravel5って表示されている」
を書いてみる。

```features/home.feature
Feature: show laravel home

  Scenario: Home
    Given I am on the homepage
    Then I should see "Laravel 5"
```

## FeatureContext.php
behatのひな形のままでは動かない、
「Behat\MinkExtension\Context\MinkContextを継承してね。」と書いてあるので読み込んでextendsを書き足す。

```features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\MinkExtension\Context\MinkContext;

/**
 * Defines application features from the specific context.
 */
class FeatureContext extends MinkContext implements Context, SnippetAcceptingContext
{
    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct()
    {
    }
}
```

DBへの変更が反映されないようにTransactionを使ったロールバック用traitや
メール送信のテストで送りたくないとき用のtraitも用意されている模様。
使う時はFeatureContextに使いたいtraitで読み込んで使うといいよとのこと。

# 動かしてみる

```
$ vendor/behat/behat/bin/behat
Feature: show laravel home

  Scenario: Home                  # features/home.feature:3
    Given I am on the homepage    # FeatureContext::iAmOnHomepage()
    Then I should see "Laravel 5" # FeatureContext::assertPageContainsText()

1 scenario (1 passed)
2 steps (2 passed)
0m1.16s (23.85Mb)
```
無事テストができた。

# 書けるテスト一覧

まだ書けるテストを把握しきれていない。
`vendor/behat/behat/bin/behat -dl`を実行すると一覧が表示されるので
これを参考にぐぐって模索。
デフォでは以下のテストが書ける。

```
$ vendor/behat/behat/bin/behat -dl
default | Given /^(?:|I )am on (?:|the )homepage$/
default |  When /^(?:|I )go to (?:|the )homepage$/
default | Given /^(?:|I )am on "(?P<page>[^"]+)"$/
default |  When /^(?:|I )go to "(?P<page>[^"]+)"$/
default |  When /^(?:|I )reload the page$/
default |  When /^(?:|I )move backward one page$/
default |  When /^(?:|I )move forward one page$/
default |  When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
default |  When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in the following:$/
default |  When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
default |  Then /^(?:|I )should be on (?:|the )homepage$/
default |  Then /^the (?i)url(?-i) should match (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^the response status code should be (?P<code>\d+)$/
default |  Then /^the response status code should not be (?P<code>\d+)$/
default |  Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default |  Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default |  Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/
default |  Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/
default |  Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
default |  Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/
default |  Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
default |  Then /^print current URL$/
default |  Then /^print last response$/
default |  Then /^show last response$/
```

テストに失敗する原因がわからないときは、下の三つを駆使して状況をみることもできる。

> default |  Then /^print current URL$/
> default |  Then /^print last response$/
> default |  Then /^show last response$/

# 次回は
2015.7.29追記　もっとテスト書いてみた
[Behat-Laravel-Extensionを使ってLaravel5のログイン認証をテストする](http://qiita.com/inaka_phper/items/d5414f245436298001e8)


このテストをこれまで構築していた
GitHubにPushしたら動くようにしたjenkinsさんで、テストが走るように設定をしてみる。

[さくらのVPSにjenkinsをインストールして、テストOKで自動デプロイする環境を構築する(1)](http://qiita.com/inaka_phper/items/f264401ed7a0bc25f407)
[さくらのVPSにjenkinsをインストールして、テストOKで自動デプロイする環境を構築する(2)](http://qiita.com/inaka_phper/items/f37c71b5bc0a7ba57157)
[さくらのVPSにjenkinsをインストールして、テストOKで自動デプロイする環境を構築する(3)](http://qiita.com/inaka_phper/items/fcb46982b3b74afe1e2c)
[さくらのVPSにjenkinsをインストールして、テストOKで自動デプロイする環境を構築する(4)](http://qiita.com/inaka_phper/items/79845a7210c48a27786b)