BehatはPHP製のBDDテストフレームワークです。
概要は下記スライドを見ておくといいでしょう。
http://www.slideshare.net/tchikuba/behatbdd
必要なライブラリのインストール
composer.jsonで必要なライブラリをインストールします。
{
"require-dev": {
"behat/behat": "3.*",
"behat/mink": "1.*",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-sahi-driver": "*",
"behat/mink-selenium2-driver": "*",
"behat/mink-zombie-driver": "*"
}
}
behat/behat
behatの本体です。
テストシナリオを自然言語に近い形で記述し、実行する機能を提供します。
behat/mink
ブラウザのエミュレーションツールと組み合わせてテストを行う機能を提供します。
behatと組み合わせて利用することが可能です。
behat/mink-extension
behatで利用できるテストの機能を追加したり、テストケースで扱える言語を拡張したりします。
behat/mink-*-driver
behat+minkのテストでブラウザのエミュレーションツールを扱えるようにします。
上記サンプルではgoutte、shai、selenium、zombiejsをインストールしていますが、これらは全てインストールする必要はなく利用するブラウザのエミュレーションツールに対応したものをインストールすればいいです。
behatでテストを行うための準備
behatのテストシナリオを置きたいディレクトリに移動してvendor/bin/behat --init
コマンドを実行します。
今回はproject/tests
以下にbehatのテストを置いて行くことを前提にします。
コマンドを実行すると以下のようなファイルが配置されます。
project/
tests/
features/
bootstrap/
FeatureContext.php
behat.ymlを用意する
behatでは実行のための設定をbehat.yml
というyaml形式の設定ファイルの記述していきます。
デフォルトではbehatコマンドの実行ディレクトリと同じ階層にあるbehat.yml
が読み込まれますが、
-c
オプションを利用することでbehat.yml
を読み込むパスを指定することも可能です。
今回はプロジェクトディレクトリ直下にbehat.yml
を置くことを想定します。
default:
suites:
default:
path: %paths.base%/tests/features
contexts:
- FeatureContext
path
behatは、ここで指定したpathのテストシナリオを読み込んでテストを実行するようになります。
contexts
behatはここで指定したクラスの機能を利用してテストを実行します。
テストの作成と実行
テストはbehat.yml
のpathに指定したディレクトリ以下に作成していきます。
(今回はproject/tests/features/
以下)
テストファイルの拡張子はfeature
である必要があります。
# language: ja
フィーチャ: 空のテスト
シナリオ: 何もしない
一行目は利用する言語の指定です。
デフォルトの状態ではテストケースが存在しないため、featureファイルにも何も記述していません。
この状態でbehat
コマンドを実行するとテストが走ります。
フィーチャ: 空のテスト
シナリオ: 何もしない # features\empty.feature:4
No scenarios
No steps
0m0.04s (10.34Mb)
テストで利用可能なケースの確認
behatではテストケースをbehat.yml
のcontext以下に記述したクラスから読み込みます。
利用可能なテストケースはbehat -dl
コマンドで確認出来ます。
デフォルトの状態では何もテストケースが登録されていないため、behat -dl
コマンドを打っても何も表示されません。
Mink Extensionのテストケースを追加する
一からテストケースを作っていくのも辛いので、
冒頭でインストールしておいたMink Extensionが提供するテストケースを利用します。
default:
suites:
default:
path: %paths.base%/tests/features
contexts:
- FeatureContext
+ - Behat\MinkExtension\Context\MinkContext
extensions:
Behat\MinkExtension:
base_url: http://google.com/
この状態でbehat -dl
コマンドを実行すると利用可能なテストケースが追加されていることが確認出来ます。
default | Given /^(?:|ユーザーは )ホームページを表示している$/
default | When /^(?:|ユーザーは )ホームページへ移動する$/
default | Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/u
default | When /^(?:|ユーザーが )"(?P<page>[^\s]+)" へ移動する$/u
default | When /^(?:|ユーザーが )ページをリロードする$/u
・
・
・
ブラウザのエミュレーションツールと合わせてE2Eテストを行う
googleのトップページから検索を行い、結果画面が表示されることを確認するテストシナリオを作成します。
# language: ja
フィーチャ: グーグル検索
シナリオ: recursionを検索
前提 ホームページを表示している
もし "q" フィールドに "recursion" と入力する
かつ "btnK" ボタンをクリックする
ならば "もしかして: recursion" と表示されていること
ブラウザのエミュレーションツールを利用するためには、behat.yml
を編集する必要があります。
default:
suites:
default:
path: %paths.base%/tests/features
contexts:
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
+ extensions:
+ Behat\MinkExtension:
+ base_url: http://google.com/
+ sessions:
+ default:
+ selenium2: ~
base_url
テスト対象のベースとなるURLを指定します。
Mink Extensionが提供するテストケースのうち、ホームページというキーワードはここで指定したURLを指します。
session
ここに利用するブラウザのエミュレーションツールを指定します。
この状態でbehat
コマンドを実行するとテストが走ります。
結果は…
フィーチャ: グーグル検索
シナリオ: recursionを検索 # features\sample.feature:4
前提 ホームページを表示している # CustomContext::iAmOnHomepage()
もし "q" フィールドに "recursion" と入力する # CustomContext::fillField()
かつ "btnK" ボタンをクリックする # CustomContext::pressButton()
ならば "もしかして: recursion" と表示されていること # CustomContext::assertPageContainsText()
The text "もしかして: recursion" was not found anywhere in the text of the current page. (Behat\Mink\Exception\ResponseTextException)
--- Failed scenarios:
features\sample.feature:4
1 scenario (1 failed)
4 steps (3 passed, 1 failed)
0m5.97s (11.00Mb)
テストケースを拡張する
先ほどのテストは検索結果の画面が表示される前に、 "もしかして: recursion" と表示されていること というテストケースが走り、テストが失敗してしまいました。
画面が表示されるまで待つ、つまり n秒待つ というテストケースが必要になります。
bootstrap/FeatureContext.php
を編集して新たにテストケースを加えます。
<?php
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
class FeatureContext implements Context, SnippetAcceptingContext
{
public function __construct()
{
}
/**
* @When :time 秒待つ
*/
public function wait($time)
{
sleep($time);
}
}
# language: ja
フィーチャ: グーグル検索
シナリオ: recursionを検索
前提 ホームページを表示している
もし "q" フィールドに "recursion" と入力する
かつ "btnK" ボタンをクリックする
+ かつ 1 秒待つ
ならば "もしかして: recursion" と表示されていること
これでbehat -dl
コマンドを実行するとテストケースが追加されていることが確認出来ます。
+ default | When :time 秒待つ
default | Given /^(?:|ユーザーは )ホームページを表示している$/
default | When /^(?:|ユーザーは )ホームページへ移動する$/
default | Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/u
・
・
・
テストを実行すると今度はパスしました。
フィーチャ: グーグル検索
シナリオ: recursionを検索 # features\sample.feature:4
前提 ホームページを表示している # CustomContext::iAmOnHomepage()
もし "q" フィールドに "recursion" と入力する # CustomContext::fillField()
かつ "btnK" ボタンをクリックする # CustomContext::pressButton()
かつ 1 秒待つ # FeatureContext::wait()
ならば "もしかして: recursion" と表示されていること # CustomContext::assertPageContainsText()
1 scenario (1 passed)
5 steps (5 passed)
0m9.29s (10.96Mb)
所感
- テストシナリオが自然言語っていうのが嬉しい、エクセルテスト表とか書きたくないし、見たくもない。
- seleniumなどのエミュレーションツールを個別に起動しないといけないのが面倒
- behat公式サイトのドキュメントがバージョン2.5のままで、最新版と思って参考にしてしまうと大いにハマる
- テスト1回にかかる時間が長いのでpushやcommit時にテスト実行というやり方はキツイ気がする
- テスト1回にかかる時間が長いのでリリース頻度の高いサービスなどではリリースブランチにマージ前に実行というのもキツイ気がする(テスト実行時間の分リリースが後ろに伸びる、テストがパスしないとさらに長い時間かけてテストするステップを踏むのは辛い)
- テスト1回にかかる時間が長いので定期的に回す程度がいい?(そうすると誰のコミットでおかしくなったか分かりにくい)
実施できるならシステムの品質を保つのにかなり高い効果が上がると思うが、
実際に運用に組み込む場合の懸念が多いのが難点かな…
E2Eテストやってるところはこのあたり、どうやって解決してるんだろう?