Technote

by sizuhiko

CakeFest2011に参加してきました

<!– more –>

昨年のシカゴに続いて2度目の参加となるCakeFest2011。今回はイギリスのマンチェスターにて行われました。 昨年のLTに続き、今年はPechaKucha形式(20秒x20枚)でCakeBehatについての紹介と、LTで日本のLTタイマ係であるドラ娘の紹介という2本の発表を行ってきました。

9/2(金):移動日

昼頃成田を出発し、同日の夜に現地に到着しました。
途中ロンドンで乗り継ぎだったのですが、行く前にイロイロと調べたらロンドンの乗り継ぎはあまり便利ではないということを聞いていて、どうなることかと思っていました。実際、ロンドン空港に到着すると案内表示版には、ほとんど搭乗ゲートが出ていません。遠いゲートまでは25分もあるというのに…. 結局30分前ぐらいに15分程度のゲート案内が出て、移動しました。丸っきりの廊下でしたが、国内線なのでそんなものなのかな、と。

で、マンチェスター空港から電車でホテルへ移動してチェックイン。ホテルのバーではワークショップからの参加者や、既に到着していた@yanodo, @cakephper と合流し、GUINNESSで空腹を満たして寝ました。空港からホテルまで、途中何も食べるところがない(というかよくわからない)というのは誤算でした。

9/3(土):カンファレンス初日

会場は手前に電源の準備された丸テーブルと、階段状の客席になっていて、自由に座ります。PCを持ってワークショップに参加している人は割とテーブル席で、そうでない人は階段席という感じでした。昨年のシカゴは全てテーブル席でみんなPCを持っていたような気がしたので、ちょっと意外な光景でした。しっかり話を聞いたり議論したりする、という雰囲気は濃かったように思います。

今回のCakeFestは部分的に2トラックになっていて、私は主にメイン会場に居ました。初日は@yandoさんがCandyCane(RedmineをCakePHPで移植したもの)の発表をして大きな反響を得ていました。私も微力ながらCandyCaneの開発に携わっていたので、この反響の大きさは嬉しかったです。その後はPHPMatsuriのサイトでも利用しているCMS Croogoについて@fahadから発表がありました。fahadは現在ロンドンで働いているということで、同じ会社からは数名のCakePHP使いが参加していました。

午後は大混雑のMarkによるPHPUnitの話を聞くために奥の部屋(いわゆる会議室)に移動していました。

Pecha Kucha Talksでの発表

本編にも申し込んでいたのですが、そちらかは落選してしまったので、当日の昼休みに夕方から始まるPecha Kuchaに申し込みました。20秒でスライドが自動送りされるという、英語が堪能ではない環境では大変難しい状況だったのですが、何とか思いは伝えられ、当日の夜や翌日には何人かの人に興味を持ってもらえました。ただ海外でもテストを書くということはあまり浸透していないようで、BDDの前にまだTDDがねー的な話があったのも、そんなに日本の状況と変わりないのだな、と思いました。

Pecha Kuchaのスライドはコチラから参照できます。

休憩時間など

CakeFestではセッション間に30分以上の十分な休憩時間が用意されており、各々中庭で歓談したりバーでビールを飲んだりと、なかなか楽しい雰囲気でした。 バーには陽気なバーテンダーがいて、場を盛り上げてくれていました。

9/4(日):カンファレンス2日目

phpnutの基調講演、国際化対応、CakePHP2.0、コアデベロッパーによるパネルディスカッションと、主にCakeDCからの発表が多かった1日でした。またMongoDBを絡めた発表も2つあり、1つは日本から参加の@cakephperさんによるMongoDB Datasource。ちょうどメイン会場が国際化対応だったので見れなかったのですが、だいぶ盛り上がっていたようです。
お昼には恒例のリアルケーキも登場。ちょうどその場にいたコアデベロッパーが揃って記念撮影。左から2番目が国際化対応の発表をしたrenanでオランダで働いているそうです。ブラジル人でもある彼は、国際化対応について問題点と解決策をわかりやすく解説していました。これは日本語圏のユーザにとってもありがたいですね。どの国でも作られたプラグインなどが国際化考慮されていれば言語ファイルなどを用意するだけで良い訳ですし。

で、マンチェスターまで行ってLTの参加枠もあるなら、せっかくなのでLTもやってきました。昨年のLTが長い話を全然打ち切れなくてすっきりしないものになっていたので、日本ではLTのタイマ係=ドラ娘という役がありまして〜という発表をしました。スライドは昨晩から少しずつ作って、長い休憩時間も使い無事完成。LTだと自分のペースで話せるので少し楽です。CakeFest参加者にはDoraGirl, DoraMUSUMEというものをバッチリ印象づけて、このネタでさらに多くの人に声をかけてもらうことができました。あのアプリ(DoraGirl)はどこからダウンロードするの?どうやって使うの?とか。

CakeFestで得たもの

もちろん最新のCakePHP2.0情報を得たり、コアデベロッパーの発表を聴くということはとても有意義なことです。ただもちろん参加するにあたり、最低1つ以上質問をしたいし、発表もしたい。幸いCakeFestでは当日申し込みのPechaKuchaやLTなどで容易に発表可能(倍率1倍 w)ということもあり、昨年の5分から今年は12分弱という時間をもらうことができました。質問はもっとも内容が理解できたMarkのPHPUnitの話でアノテーションの使いどころなどに関して聞きました。

また発表や質問を通じて、コアデベロッパーにアイツは何者?とか、こういうことをやっている人、例えば私だとなんかやたらテストについて発表したり質問したりする人、という印象で持ってもらえていると思っています。(事実Markに関してはそういう風に声をかけてくれる。)

で、じゃぁすぐに何かに役立つわけではないかもしれないけど、何かコア部分に関してやりとりしたくなったときや、もちろん日本のイベントに来てもらえるということも、そういった事の積み重ねではないかなぁ、と思っているのです。ですので、来年は日本からより多くの参加者がCakeFestに行ってもらえると嬉しいなぁと思っています。

その他、雑多にふりかえると

ビールが安い!

例えばGUINNESSのパイントが約3ポンド。500しないぐらい。日本の半額以下ですね。これなら毎日相当量飲めそう。事実他のビールも含めだいぶいっぱい飲みましたが…

マンチェスター・ユナイテッド博物館がいい!

イギリスでのサッカーの歴史を感じる博物館で、展示内容もすばらしかったです。

あと全般、ずっと雨模様だったり、涼しかった(寒いに近いぐらい)だったり。 帰国日はマンチェスター空港からの出発が天候不良で1時間近く遅くなり、ロンドンに付いたら1時間半遅れ、3時間の乗り継ぎ時間があったのですが、おみやげ買ったりロンドン空港内の移動(すごい遠い)したりしていたら全く余裕がなく帰国便に搭乗となりました。ロンドン経由にするなら乗り継ぎ時間にかなり余裕を見ておいた方がいいなぁと感じました。

ロンドン空港はオリンピックに向けて、新ターミナルを作っていたりとまた大きくなる印象。さらに複雑になるのかなぁ〜

CakePHPアプリケーションをBehatでテストする

はじめに

Behatとは、Ruby on Railsでは有名なBDDフレームワークcucumberのPHP版クローンです。以前私が発表した資料が過去記事にありますので、詳しくはそちらを参照ください。

利点は、顧客が理解できるシナリオを自然言語(つまり日本語)で記述し、それ自体がテスト実行可能であるということです。

今までCakePHPアプリケーションのテストは単体テストではSimpleTest、ブラウザベースのテストはSeleniumを使うことが多かったと思いますが、これからはBehat/Minkによってテストの幅が広がるでしょう。

しかし、BehatはSymfonyベースで、これまでCakePHPのアプリケーションを実行するためのプラグインなどは準備されてきませんでした。そこで、「rake cucumber」に習い「cake behat」として実行できるシェルタスクを用意しました。

それが「CakeBehat」シェルです。

https://github.com/sizuhiko/CakeBehat

インストール

前提事項

  1. gitが利用可能なこと
  2. PHPUnitがインストールされていること
  3. CakePHPがインストールされていること
  4. MySQLなどデータベースがインストールされていて、テスト用データベースが準備されていること

導入

CakePHPのvendorsディレクトリへ移動して、以下のコマンドを実行してください。

git clone git@github.com:sizuhiko/CakeBehat.git
git clone git://github.com/Behat/Behat.git && cd Behat
git submodule update --init
cd ..
git clone git://github.com/Behat/Mink.git && cd Mink
git submodule update --init

すると、vendors 配下に CakeBehat, Behat, Mink の3つのディレクトリができているはずです。

次に、CakeBehat/vendors/shells から behat.php と behat.yml.default を CakePHPのvendors直下へコピーします。

すると、vendors直下には、behat.php, behat.yml.default, CakeBehat, Behat, Minkがあるはずです。

続いて、CakeBehat/features を CakePHPのappやcakeと同じディレクトリ階層にコピーします。

初期導入は以上で終了です。

環境設定

Behat/Mink環境設定

vendors直下にコピーしたbehat.yml.defaultをbehat.ymlにコピーします。

4行目に

      start_url: http://test.localhost:8888/application-name/

という設定があります。これはアプリケーションのルートパスを設定するもので、ホスト名、ポート番号、アプリケーション名などを指定します。ホスト名はできるだけtest環境と識別可能なものにしておく事がオススメです。

これでおわり?

基本的な環境設定は、ここまでで、behatは実行可能な状況になっています。では具体的にサンプルアプリケーションのコードを使って解説します。

サンプルアプリケーション(ブログ)

CakeBahet/sample の中にCookBookの「CakePHPブログチュートリアル」の初期状態(Postの一覧と詳細だけ)のコードを準備しました。

データベースの切り替え

config/database.phpはそのまま生成すると$defaultがあります。UnitTestを実施する場合などは$testを作成していることもあるでしょう。Behat/Minkはブラウザアクセスで実行されるので、工夫が必要になります。またテストデータの投入で$testを参照するので、必ず定義が必要になります。 そこで Bakery の「Easy peasy database config」にも書いてるように、環境によってデータベース設定が切り替えられるように対応します。

このサンプルでは、アクセスされたサーバ名を基準に切り替えるようにしており、test.localhost なら test用データベース、それ以外ならdevelopment用データベースを利用するようにしています。

hostsファイルに 127.0.0.1 localhost, test.localhost としておけば、test.localhostでアクセス可能になるでしょう。簡単ですね。

テストシナリオの記述

Behatのシナリオ記述は基本的にcucumberと同じです。もし基本的な記述方法がわからない場合は、達人出版社「はじめる! Cucumber」を読むと良いと思います。

ブログチュートリアルの一覧表示と、詳細表示のテストとして、以下のようなシナリオを書きました。

sample/features/posts.features

# language: ja
フィーチャ: ブログの記事を閲覧した
  なぜならブログの記事を閲覧することで、最新の情報を入手したいからだ

  背景:
    前提 ブログ記事に以下の内容が登録されていること:
      | タイトル | 本文 |
      | タイトル | これは、記事の本文です。 |
      | またタイトル | そこに本文が続きます。 |
      | Title strikes back | こりゃ本当にわくわくする!うそ。 |

  シナリオ: 記事一覧を閲覧できること
    前提 トップページ を表示している
    ならば "タイトル" と表示されていること
    かつ "またタイトル" と表示されていること
    かつ "Title strikes back" と表示されていること

  シナリオ: 記事の本文を閲覧できること
    前提 トップページ を表示している
    かつ "またタイトル" のリンク先へ移動する
    ならば "そこに本文が続きます" と表示されていること

どうですか?最低限の決まりがあるように見えますが、普通のドキュメントとして読む事も可能ですよね?少なくともプログラマでなければ読めないSeleniumのようなテストコードとは違うと思います。これがBehatの特徴でもあります。

Hint

Behat/Minkでどんなステップ記述が利用可能かどうかは、

cake/console/cake behat --steps --lang ja

のように実行すると一覧表示されます。

テストデータの登録

サンプルでは「ブログ記事に以下の内容が登録されていること:」のように記述しました。これは最初から用意されているものではなく、独自に定義する必要があるステップです。ステップは features/steps の下に phpファイルを作成すれば自動的に読み込まれ、利用可能になります。

sample/features/steps/posts_step.php

$steps->Given('/^ブログ記事に以下の内容が登録されていること:$/', function($world, $table) {
  $hash = $table->getHash();
  $world->truncateModel('Post');
  $post = $world->getModel('Post');
  foreach ($hash as $row) {
    $post->create(array('Post'=>array('title'=>$row['タイトル'], 'body'=>$row['本文'])));
    $post->save();
  }
});

ステップの雛形自体は、ステップが存在しないときにBehatを実行すると、以下のように画面表示されるので、そのままコピペして作成すると簡単です。

CakeBehatではテストデータを登録するのが容易になるように、Modelを取得できるようにしています。またテストデータを消去するためにtruncateできる仕組みも用意しました。

  1. データの削除:$world->truncateModel(‘Post’);
  2. モデルの取得:$post = $world->getModel(‘Post’);

後は、AAで書いた表をそのまま利用できる(1行目が自動的にタイトル行として解釈されています)ので Model->createとModel->saveを利用すれば、いつものCakePHPの感覚でデータを登録できるはずです。 データベースは自動的に$testで定義された宛先に接続するようになっています。

実行してみよう

cakeやappのディレクトリに移動して、以下のコマンドを実行します。

cake/console/cake behat

さいごに

基本的なアプリケーションであれば、テスト可能な状況になっていると思います。

何かあれば、CakeBehatをforkしてアップデートに参加してもらえればと思います。

現時点で気になっているのは、

  1. モデルの初期化を shells/behat.php の _loadModels() で実行しているのですが、App::objects(‘model’)でモデルの一覧を取得しているので、プラグインのModelまで初期化できていません。ここで初期化する理由は、Shell(CakePHP) -> Behat(Symfony) -> CakePHPのように呼び出されると、初期化されていないモデルが利用できないので、事前に全てClassRegistry::initしておくことで、この問題を回避しています。プラグイン内のモデルを使う場合は、この記述を変更して、プラグインのモデルも利用可能にする必要があります。
  2. テストデータの削除を明示的にtruncateModelしないといけない。本来は features/support/hook.php のフックポイントでtruncateModelを呼び出せるように仕込んでおきたかったのですが、フックポイントから呼び出すとSTRICTエラーが出てしまい、うまく動作しません。ということから、データ投入ステップで一度データを削除するように記述しています。
  3. GithubのReadmeとWikiがまだ未着手で。。

といったところです。

このネタで、CakeFestに応募しようと思うのですが、後9日か。。。

PHPカンファレンス関西に参加してきました

<!– more –>4/2に開催されたPHPカンファレンス関西に参加してきました。

実行委員の皆様お疲れさまでした。ものすごい楽しいイベントでした。

僕はLTでBehatの話をしたのですが、他のプレゼンテーションがとてもバラエティにとんでいて、とても勉強になったし、交流もできて良かったです。

プログラムの内容や、それぞれの内容は他の参加者の方が詳しく書いていたりするので、簡単にKPT形式で振り返っておきます。

※アンケート書いたけど、やっぱり落ち着いたところで考えると違いますね。

KEEP

  1. 前日入りは楽
  2. 会場がキレイ(税金がふんだんに投入されている感のある・・・以降自粛)
  3. 関西での大規模イベント(東京からの参加でしたが。。。)
  4. スムーズな進行、時間通りに進んでいて感動した
  5. 懇親会LTも大スクリーンで見やすい
  6. 懇親会の食事が美味しい
  7. ドラ娘がかわいい

Problem

  1. 電源が少ない。自分はAirだし、Wifiルータも10時間持つURoad-9000だったので大丈夫でしたが、全体的に困っていた人が多かった印象
  2. 欲を言えば2トラックぐらいあると良かったかも(もっとコード的な欲望がある人向けセッションみたいな)
  3. 16Fのレストランはテンパっていた w (本編に関係ないですが)
  4. 懇親会同一会場は待ち時間が微妙
  5. (本編LTで)ドラ娘の仕事が少なかった(懇親会では大変そうでした。お疲れさまでした)
  6. LT発表者が東方面だった

Try

  1. 次はハッカソン? (ザワっ
  2. 青年団も何か告知できるようにしておきたかった (謎
  3. また来年もあれば参加したい

で、スライドなんですが、いつものslideshareにアップしてたんですが、何度やっても失敗するようになったので、おさらばすることにしました(過去のファイルはそのまま置いておきますが)。取り急ぎ、MyOperaのストレージに置いたので、こちらをダウンロードして見てください。

また、懇親会のジャンケン大会で本をもらいました。

最後に、東京に帰る新幹線で、WiMAXルータURoad-9000がどのくらい使えるか検証してみました。

  1. トンネル以外は概ね問題なく接続でき、とても快適
  2. トンネル以外での不調エリアは、米原、浜名湖近辺だけ
  3. 上記いずれも体感です(ご了承ください)

ということで、N700系であればWifi使えるけど、それ以外どうする?といった場合、WiMAXも十分行けますよ、あなた。(宣伝)

Behatのインストールについての注意事項

<!– more –>4/2 に行われるPHPカンファレンス関西にて、BehatについてLTをすることになりました。

そこで最近Behatをダウンロードしてみたのですが、git submoduleでのSymfonyコンポーネントのインストールで問題がありますので、解決策を書いておきます。

  1. Behat 本体のインストール

    git clone git://github.com/Behat/Behat.git

  2. サブモジュールのインストール

    cd Behat git submodule update –init

とくに問題がなければ、しばらくして終了しますが、本稿執筆次点では以下のようなエラーで中断してしまいます。

Initialized empty Git repository in /Behat/vendor/Symfony/Component/Translation/.git/
remote: Counting objects: 220, done.
remote: Compressing objects: 100% (124/124), done.
remote: Total 220 (delta 147), reused 141 (delta 94)
Receiving objects: 100% (220/220), 52.61 KiB, done.
Resolving deltas: 100% (147/147), done.
fatal: reference is not a tree: d5cdaba8550b7b99f37d2aad345dd8ebbe3efb9c
Unable to checkout 'd5cdaba8550b7b99f37d2aad345dd8ebbe3efb9c' in submodule path 'vendor/Symfony/Component/Translation'

そこで、エラーになったサブモジュールの変更ログを確認します。

git log --oneline -p -- vendor/Symfony/Component/Translation

上記コマンドを実行すると、以下のように表示されます。

85d8dac updated vendors
diff --git a/vendor/Symfony/Component/Translation b/vendor/Symfony/Component/Translation
index f70e0ba..d5cdaba 160000
--- a/vendor/Symfony/Component/Translation
+++ b/vendor/Symfony/Component/Translation
@@ -1 +1 @@
-Subproject commit f70e0ba1b04e7db4b3aa6d122470291d4fb34732
+Subproject commit d5cdaba8550b7b99f37d2aad345dd8ebbe3efb9c
da2503a updated submodules
diff --git a/vendor/Symfony/Component/Translation b/vendor/Symfony/Component/Translation
new file mode 160000
index 0000000..f70e0ba
--- /dev/null
+++ b/vendor/Symfony/Component/Translation
@@ -0,0 +1 @@
+Subproject commit f70e0ba1b04e7db4b3aa6d122470291d4fb34732

ここで表示されたログの1行目「85d8dac updated vendors」の85d8dacを使ってチェックアウトします。

git checkout 85d8dac~ -- vendor/Symfony/Component/Translation
git submodule update

こうすることで、サブモジュールのインストールが継続できます。

  1. 実行する

    bash-3.2$ ./bin/behat.php ……………………………………………………………. ……………………………………………………………. ……………………………………………………………………….

    36 scenarios (36 passed) 222 steps (222 passed) 0m7.045s

成功したら、インストールは完了です。

PHPのBDDフレームワーク Behat について発表してきました

第55回PHP勉強会@関東で発表してきました

2011/2/10にPHP勉強会が約半年ぶりに開催されました。以前、発表するぜーと公言していたので、今回は万全を期してと思いきや最近の多忙に負けそうになりました。が、前日(というか当日朝4時)までバタバタしながら準備してました。。。

今回は昨年のPHP祭りで日本語ハックしたBehatの入門編です。あのときはLTという形であまり詳細に触れられなかったので、30分という時間ですが実際に1からやってみました。 ここでは実際に「やってみよう」で実践した手順について解説します。

はじめに

スライドの「1. インストール」でも書いていますが、まずBehat本体をインストールしてください。私がforkした日本語対応版をcloneすると「3. 日本語環境」の部分をスキップできます。 「2. テスト環境」に関しては、今回behat_webstepsを流用しました。それ以外にもGoutte-for-Behat(https://github.com/ThePixelDeveloper/Goutte-for-Behat)というものもあります。どちらもHTMLクライアントのエンジンとして「Goutte」を使っています。前者はgoutte.pharが含まれていますが、後者は含まれていないので別途Goutteをcloneする必要があります。ただ後者の方がstepsのコードは流用しやすいかもしれません。

準備しよう

まず初期状態のディレクトリを以下のようにしました。

|-- Behat Behat本体のclone
|-- behat_websteps behat_webstepsのclone

練習環境を作る

behat_webstepsを土台にして、テスト環境を作ります。といっても、まずは単にコピーするだけです。

cp -R behat_websteps sandbox

フィーチャーファイルを作成する

スライドでも引用したgihyo.jpでのcucumberについてとてもわかりやすい連載「第22回 Railsアプリの受け入れテストをCucumberで書こう」のフィーチャーを使用します。

vi sandbox/features/sample.feature

実際のコードは以下のとおりです。

# language: ja
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録
    前提 "ユーザ登録"ページを表示している
    もし "email"に"example@example.com"と入力する
    かつ "name"に"赤松 祐希"と入力する
    かつ "age"に"22"と入力する
    かつ "Create"ボタンをクリックする
    ならば "User was successfully created."と表示されていること
    かつ "example@example.com"と表示されていること
    かつ "赤松 祐希"と表示されていること
    かつ "22"と表示されていること

ここでcucumberと違うのは1行目(フェーチャの前)に言語指定をするところです。現時点のBehatではこれを書く事でja.xmlを利用してくれます。

実行してみる

フィーチャーはbin/behatコマンドでテストします。

bash-3.2$ Behat/bin/behat sandbox/features/sample.feature 

画面には、以下のように出力されます。

<code>
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録                                     # features/sample.feature:4

<span style="color: goldenrod">
    前提 "ユーザ登録"ページを表示している
    もし "email"に"example@example.com"と入力する
    かつ "name"に"赤松 祐希"と入力する
    かつ "age"に"22"と入力する
    かつ "Create"ボタンをクリックする
    ならば "User was successfully created."と表示されていること
    かつ "example@example.com"と表示されていること
    かつ "赤松 祐希"と表示されていること
    かつ "22"と表示されていること
</span>
1 scenario (<span style="color: goldenrod">1 undefined</span>)
9 steps (<span style="color: goldenrod">9 undefined</span>)
0.123s
<span style="color: goldenrod">
You can implement step definitions for undefined steps with these snippets:

$steps->前提('/^"([^"]*)"ページを表示している$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});

$steps->もし('/^"([^"]*)"に"([^"]*)"と入力する$/', function($world, $arg1, $arg2) {
    throw new EverzetBehatExceptionPending();
});

$steps->かつ('/^"([^"]*)"ボタンをクリックする$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});

$steps->ならば('/^"([^"]*)"と表示されていること$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});
</span>
</code>

ステップ(テストの定義)が記述されていないので、9つの未定義エラーが表示されています。その下にはスケルトンが表示されているので、まずこれをコピペしてstepファイルを作成します。

ステップファイルの作成

stepファイル「webteststep.php」に前節のスケルトンを貼付けます。以下のようになるでしょう。

<?php

$steps->前提('/^"([^"]*)"ページを表示している$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});

$steps->もし('/^"([^"]*)"に"([^"]*)"と入力する$/', function($world, $arg1, $arg2) {
    throw new EverzetBehatExceptionPending();
});

$steps->かつ('/^"([^"]*)"ボタンをクリックする$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});

$steps->ならば('/^"([^"]*)"と表示されていること$/', function($world, $arg1) {
    throw new EverzetBehatExceptionPending();
});

また実行してみましょう。

<code>
bash-3.2$ Behat/bin/behat sandbox/features/sample.feature 
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録                                     # features/sample.feature:4

<span style="color: goldenrod">
    前提 "ユーザ登録"ページを表示している                           # features/steps/web_test_step.php:5
      TODO: write pending definition
</span><span style="color: skyblue">
    もし "email"に"example@example.com"と入力する          # features/steps/web_test_step.php:9
    ...(** 省略 **)
</span>

1 scenario (<span style="color: goldenrod">1 pending</span>)
9 steps (<span style="color: skyblue">8 skipped</span>, <span style="color: goldenrod">1 pending</span>)
0.089s
</code>

未定義(undefined)から、ペンディングに変わりましたね。これは貼付けたコードが現時点でPending例外を投げているためです。

ステップを記述する

ステップの中身はbehat_webstepsのcommon.phpを流用しました。

<?php

$steps->前提('/^"([^"]*)"ページを表示している$/', function($world, $page) {
  $page = $world->__getPath($page);

  $world->client->request('GET', $page);
  $world->__getClientProperties();
});

$steps->もし('/^"([^"]*)"に"([^"]*)"と入力する$/', function($world, $field, $value) {
  assertNotNull($world->page,"No webpage loaded");
  $form = $world->__getForm('Create');
  $form[$field]->setValue($value);
});

$steps->かつ('/^"([^"]*)"ボタンをクリックする$/', function($world, $button) {
  assertNotNull($world->page,"No webpage loaded");
  $form = $world->__getForm('Create');
  $world->client->submit($form);
  $world->__getClientProperties();
});

$steps->ならば('/^"([^"]*)"と表示されていること$/', function($world, $text) {
  assertNotNull($world->page,"No webpage loaded");
  assertContains($text,$world->output);
});

実行してみましょう。

<code>
bash-3.2$ Behat/bin/behat sandbox/features/sample.feature 
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録                                     # features/sample.feature:4

<span style="color: firebrick">    前提 "ユーザ登録"ページを表示している                           # features/steps/web_test_step.php:8
      Unknown path 'ユーザ登録'. You can define it in [features_folder]/support/paths.php
      Failed asserting that an array has the key <string:ユーザ登録>.
</span><span style="color: skyblue">    もし "email"に"example@example.com"と入力する          # features/steps/web_test_step.php:14
    ...(** 省略 **)
</span>

1 scenario (<span style="color: firebrick">1 failed</span>)
9 steps (<span style="color: skyblue">8 skipped</span>), <span style="color: firebrick">1 failed</span>)
0.355s
</code>

すると「ユーザ登録」なんてURLは見つからないというエラーになります。まぁそうですね。URLに関しては画面名と実際のhttpリクエストするURLを関連づけておく必要があります。これはgoutte以外のHTTPクライアントを使っても同じことです。ベースとしたbehat_webstepsではsupport/paths.phpにそのマッピングを書くようにしていますので、追加します。

<?php
$world->paths = array();
$world->paths['ユーザ登録'] = "http://localhost/user_regist.php";

テストを失敗させる

ここまでで、初期段階の準備は完了です。ではまたテストを実行してみましょう。

<code>
bash-3.2$ Behat/bin/behat sandbox/features/sample.feature 
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録                                     # features/sample.feature:4

    <span style="color: green">前提 "ユーザ登録"ページを表示している                           # features/steps/web_test_step.php:8</span>
    <span style="color: firebrick">もし "email"に"example@example.com"と入力する          # features/steps/web_test_step.php:14
      The current node list is empty.</span>
    <span style="color: skyblue">...(** 省略 **)</span>

1 scenario (<span style="color: firebrick">1 failed</span>)
9 steps (<span style="color: green">1 passed</span>, <span style="color: skyblue">7 skipped</span>, <span style="color: firebrick">1 failed</span>)
0.114s
</code>

テストを実行してみると、最初に入力フォームに値をセットしようとするところで失敗します。まだユーザ登録画面を表示するプログラムはありません。

それでは実際に動作するコードを書いてみましょう。

実装する

ここではあくまでテストを通過させる簡単なコードを書いてみます。画面の入力フォームから入力された値を表示するuser_regist.phpを書いてみましょう。

<!DOCTYPE html>
<html>
    <head>
      <title>ユーザ登録</title>
    </head>
    <body>
        <div><?
            if ($_POST["submit"]) {
                echo "User was successfully created.". "<br />";
                echo "name = "     . $_POST['name']  . "<br />";
                echo "e-mail = "   . $_POST['email'] . "<br />";
                echo "age = "      . $_POST['age']   . "<br />";
            }
        ?></div>
        <form method="post" action="user_regist.php">
            <div>
                <label>名前:</label><input type="text" name="name" value="">
            </div>
            <div>
                <label>e-mail:</label><input type="text" name="email" value="">
            </div>
            <div>
                <label>年齢:</label><input type="text" name="age" value="">
            </div>
            <div>
                <input type="submit" name="submit" value="Create">
            </div>
        </form>
    </body>
</html>

※まぁなんとも安直なコードですが、ご勘弁を・・・

作成したファイルをドキュメントルートに設置してテストを実行しましょう。

<code>
bash-3.2$ Behat/bin/behat sandbox/features/sample.feature 
フィーチャ: ユーザを管理したい

  シナリオ: ユーザの登録                                     # features/sample.feature:4

<span style="color: green">    前提 "ユーザ登録"ページを表示している                           # features/steps/web_test_step.php:8
    もし "email"に"example@example.com"と入力する          # features/steps/web_test_step.php:14
    かつ "name"に"赤松 祐希"と入力する                         # features/steps/web_test_step.php:14
    かつ "age"に"22"と入力する                             # features/steps/web_test_step.php:14
    かつ "Create"ボタンをクリックする                          # features/steps/web_test_step.php:21
    ならば "User was successfully created."と表示されていること # features/steps/web_test_step.php:26
    かつ "example@example.com"と表示されていること             # features/steps/web_test_step.php:26
    かつ "赤松 祐希"と表示されていること                           # features/steps/web_test_step.php:26
    かつ "22"と表示されていること                              # features/steps/web_test_step.php:26
</span>
1 scenario (<span style="color: green">1 passed</span>)
9 steps (<span style="color: green">9 passed</span>)
0.389s
</code>

やったー、グリーンでテスト成功です。

さいごに

今回はbehatwebstepsをベースにしましたが、HTTPクライアントは何でも大丈夫です。今回goutteでしたが、BehatはPHPUnitを使うのでPHPUnitのPHPUnitExtensions_SeleniumTestCaseを使うのも1つでしょう。Javascriptが使われているサイトなどでは、こちらの選択になりますね。

大事なことはアジャイルでドキュメントを書く事と、無駄なドキュメントでなく実行可能なドキュメントである、ということですね。プレーンテキストなのでプログラマじゃなくても、お客さんでも読めるし、書いてもらう事も可能かもしれません。打ち合わせでフィーチャファイルを議事録的に書きながら進めることもできると思います。

今後の展開としては、一般的なwebstepに関してはgoutteを使った日本語版を、私のgithubアカウントで公開予定です。4/2のPHPカンファレンス関西までには公開して何かしゃべりたいなーと思っていますが、ちょっとテーマと違うか。。LT狙いで何か。。

Behat RC1も期待して待ちましょう!!

懇親会でも話題になったのですが、PHPSpecはどこへ行ってしまったのか・・・

それと、pharファイルが使えないよ!という方(当日朝3時までのオレ)、はこちらの「Pharは便利だけど --enalbe-zend-multibyteが有効だと文字化けしてしまう」を参考にしてください!!