Technote

by sizuhiko

CakePHPのコントローラテストで注意すること

コントローラのテストは難解である

とは言え、テストを書かないというのも何なのでテストを書くわけですが。

CakePHPではControllerTestCaseというテストケースクラスを継承してテストケースを書くのですが、Cakeのテストにモックが導入される前は testAction のオプションに PostsTestController のような PostsController を継承したクラスを作成し、内部的にそれを利用するように渡していました。

class PostsTestController extends PostsController {
// モックしたい処理
....
}
class PostsControllerTest extends ControllerTestCase {

    public function testアクションをテストする() {
        $this->testAction('url', array('controller'=>'PostsTestController'));
    }
}

ただし現在のCakePHP 2.3や2.4系では、モックオブジェクトを利用します。

なぜモックが必要?

もしこの記事を読んでいる方で、testActionがブラウザからも、コンソールからも成功するよ、という場合は依存関係が少ないか、たまたまなのかもしれません。

Webアプリケーションのコントローラは様々な依存関係があり、簡単にテストを通過させることができません。特にSessionComponentを使ったactionをテストする場合、問題が発生します。
Sessionはブラウザからテストを実行している範囲では問題が起きないのですが、コンソールで実行するとエラーになるという事があります。まぁコンソールにはセッションなんてないからね。当然です。

しかし実装上はセッションから値を取り出して何か処理をするという事がありえるわけです。

// 実際のコントローラ
    $components = ['Session', 'Auth'];

    public function index() {
        $hoge = $this->Session->read('hoge');
        .....
    }

// テストケース
    public function testIndex() {
        CakeSession::write('hoge', 'fuga');
        $this->testAction('/posts/index');
        ......
    }

みたいなコードがあった場合にindexアクションをテストすると、ブラウザからは成功するのですが、コンソールからだと失敗する。経験したことありませんか?

どうやってモックを使うのか

で、調べてみたら意外と解説してある部分がない。こういうときはCakePHPのコアテストコードを見るのが一番です。
具体的には lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php です(lib/Cake/Test/Case/Controller/…でないところがミソ)。

で、上記コードを参照した上で先ほどのテストコードを改修すると、以下のようになります。

    public function testIndex() {
        $this->controller = $this->generate("Posts", ['components' => ['Session']]);
        $this->controller->Session->expects($this->any())
             ->method('read')
             ->will($this->returnValueMap([['hoge', 'fuga']]));
        $this->testAction('/posts/index');

$this->generate() はControllerTestCaseが定義しているモックを作成するためのメソッドで、第一引数は PostsController の Controller 部分を除いた名前です。第二引数には components や helper、model など、コントローラから依存関係にあるモジュールを同時にモックしたい場合に配列形式で記述します。今回はSessionコンポーネントをモックしたいので、第二引数に指定します。
このgenerate()はコントローラの初期化や依存関係のモック処理もやってくれるとても強力な機能を持っています。PHPUnitのgetMockでモックするのではなく、必ずこのメソッドを使うようにしましょう。

generate()の戻り値はコントローラのモックオブジェクトになっています。さらにSessionもモックしているので、 $this->controller->Session に対して、read(‘hoge’)はいつでも'fuga'を返すという記述をすることが可能です。
モック自体はPHPUnitのモックオブジェクトなので、詳しい記法はPHPUnitのマニュアルからモックオブジェクトの章を参照してください。

PHPUnit3.7のモックオブジェクト解説

最後に

コントローラのテストは本当に難解です。ただコアのテストコードを見るだけでもだいぶ理解が深まります。一度自分のアプリでテストを書く前に、その親クラス(ModelとかControllerなど)やテストケースのテストコードを見ると、これまでにテストの書き方がわからなかったところも、腑に落ちることがあるはずです。

CakePHP BddPlugin updates (2013/2/19)

v0.9.3リリース

本日バージョン0.9.3をリリースしました。

機能面での修正というわけではなく、今回は Composer の A Multi-Framework Composer Library Installer (https://github.com/composer/installers) という仕組みに対応したものです。

なのでpluginsディレクトリでgit cloneして引き続きご利用の皆様は、今回のアップデートは無視してもらっても大丈夫です。

なにが嬉しいのか

Composerのフレームワークプラグインを管理する機能については、ちょっと前から知っていたのですが、まぁCakePHPでは3以降中心かな?と思っていたところ @abe4tawa8 さんから Pull Request をいただきまして、修正を始めたといういきさつです。

このComposerの仕組みを使うと、appディレクトリの下に利用するプラグインとかPHPUnitとかのrequireを指定したcomposer.jsonを置いておくことでプロジェクトで必要なモジュールの依存関係を解決しようというものです。

例えば

{
    "config": {
        "vendor-dir": "Vendor"
    },
    "minimum-stability": "dev",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.phpunit.de"
        },

        {
            "type": "vcs",
            "url": "https://github.com/sizuhiko/CommonContexts.git"
        },
        {
            "type": "vcs",
            "url": "https://github.com/sizuhiko/Spec-PHP.git"
        },

        {
            "type": "vcs",
            "url": "git://github.com/sizuhiko/Bdd.git"
        }
    ],
    "require-dev": {
        "sizuhiko/Bdd": "dev-develop",
        "behat/mink-goutte-driver": "*",
        "behat/mink-selenium-driver": "*",
        "behat/mink-selenium2-driver": "*"
    }
}

のように書くと、Bddプラグインを app/Plugins ディレクトリにインストールすることが可能になります。

あとは app/Config/bootstrap.php に

CakePlugin::load('Bdd');
require dirname(dirname(__FILE__)).DS.'Vendor/autoload.php';

BddPluginの利用と、依存関係のライブラリ群のautoloadをrequireすれば大丈夫です。

残念なお知らせ

Composerでは依存先のライブラリのrepositories指定を参照してくれません。

Repositories are not resolved recursively. You can only add them to your main composer.json. Repository declarations of dependencies’ composer.jsons are ignored.

http://getcomposer.org/doc/04-schema.md#repositories より

BddPluginではPullRequest中のCommonContextやforkしてカスタマイズしているSpec-PHPを使っており、それらのリポジトリを指定する必要があります。

他の依存関係はどうなっているの?と、気付いた方はするどいわけですが、ComposerはPackagistというリポジトリサイトを使っており、ここに必要なリポジトリを追加すれば良い訳です。登録は無料です。

で、これらforkしたプロジェクトについても登録してしまえば良いと思って作業を進めていたのですが、いざ手元でも動作確認がほぼOKになったところでPackagistのサイトでSubmitしようとすると

Do not submit forks of existing packages. If you need to test changes to a package that you forked to patch, use VCS Repositories instead. If however it is a real long-term fork you intend on maintaining feel free to submit it.

特に「*Do not submit forks of existing packages. *」の部分は太字になっていまして、他を受け付けない雰囲気を醸し出しています。まぁ要はメンテナンスを続けるならフォークしたプロジェクトでも登録可能だよ、と書いてあるわけですが、あまりの警告にビビってしまい各アプリケーションのcomposer.jsonにrepositoriesを追加してもらう方針としました。必要な部分は上記サンプルのとおりになっています。

さいごに

そういえばBddPlugin自体はPackagistに登録しても問題ないのだった!、と今Blogを書いていて気がついたので、それはこれから実施します。そうするとBddPluginのリポジトリ宣言は必要なくなりますね(汗

今のcomposer.jsonではnameがsizuhiko/Bddになっていて、BddをスモールケースにしろとPackagistに怒られているので、少々時間がかかります

それと先日Co-Edoで行われたCakeBeerTalkの飛び入りLTでも話しましたが、使っていての不明点など何でもgithubのissueで質問ください。よろしくおねがいします。

BddPluginを使った勉強会もやりたいですなー

CakePHP BddPlugin updates

v0.9.2リリース

CakePHPのBddプラグインも少しずつ浸透し、国内外より利用者様の声が届いて、いくつか不具合&機能改善などを取り込んできました。

本日リリースしたv0.9.2としては、数回にわたり@kaz_29さんと、もくもく会を重ねた結果が入っています。

  • selenium2 webdriver対応(これは0.9.1.xで対応済みでしたが)
  • spec(単体テスト)でコードカバレッジを出力できるようにした
  • story(Behat)のステップ翻訳ファイルを置き換えられるようにした
  • プラグイン内のコードインデントを4tabに統一

0.9.2よりちょっと前の改修について

v0.9.1においては、Authコンポーネントを利用した場合にうまく動作しないというポレートを海外から受けました。

これはAuthの問題というよりも、Authでログインした結果リダイレクトが動くので、リダイレクト先が正しいか検証したい、ということでした。

これはBehat&Mink側の問題だけど、使ってくれているのでMLとか漁ったところ、goutte&guzzleではパラメータを指定しろ、という変更が入っていることがわかり、behat.yml.defaultに以下の設定を追加しました。

      goutte:
        guzzle_parameters:
          request.params:
            redirect.disable: true

その上で、サンプルアプリにAuthのケースを追加し、動作検証後にリリース。質問者に返信を打ってIssueをCloseしました。

リダイレクトURLを検証する方法としては、以下のサンプルシナリオのようになります。

  シナリオ: 記事を追加できること
    前提 "トップページ" を表示している
    もし "追加" のリンク先へ移動する
    かつ "bob"、"obo"でログインする
    かつ I do not follow redirects
    かつ 記事投稿フォームに以下の内容を登録すること:
      | ラベル  | 値                |
      | タイトル | 今日は歓送迎会      |
      | 本文    | 19:30からせかいいち |
    かつ "投稿" ボタンをクリックする
    もし I should be redirected to "/posts"
    ならば "今日は歓送迎会" と表示されていること
  • I do not follow redirects と宣言することで、リダイレクトがHTTPヘッダに入っていても自動ではリダクレクとしなくなります(redirect.disable: trueが有効になる)。
  • 「"投稿“ ボタンをクリックする」の後で一覧画面にリダイレクトされるので、I should be redirected to ”/posts" のように指定したURLにリダイレクトしようとしているか検証します。検証結果が正しければ、そのURLに遷移してテストが継続します。

つまり自動リダイレクトをOFFにして、検証が成功したら遷移して継続という流れです。
日本語のステップにはなっていませんが、必要であれば独自のステップ定義を作っていただき、そこから英語ステップを呼び出すという方式でもできると思います。ただ、ここまでテクニカルというか内部動作をシナリオに書くのはユーザーストーリーとしては微妙だと思うので、ステップ定義内で使えるステップぐらいの認識で利用していただければと思います。サンプルアプリのストーリーは、問い合わせのあった形式に合わせているので、そこはあまり意識されていません。

0.9.2ハイライト

コードカバレッジ

みんな大好きJenkinsなどでCI環境を作ってコードカバレッジなどを計測している現場では、PHPUnitではカバレッジレポートが出せるのに、BddPluginでは出ないのはアレだよね、ということで対応しました。

lib/Cake/Console/cake Bdd.spec --coverage-html report

でコードカバレッジを出力できるようになっています。

  • 出力できるレポートはPHPUnitがサポートしている coverage-html, coverage-clover, coverage-php, coverage-textの4つです。
  • カバレッジ対象のディレクトリは app 以下です(ただしapp/Pluginおよびapp/Vendor, app/Testは対象外です)。

Bdd.story側はseleniumとの兼ね合いがあって単純ではないので、shin1x1さんの「コードカバレッジ測定ツールPHP_CodeCoverageをCakePHPで使ってみた」を参照にすると良いかなと思います。細かいコードの利用方法などはPHPUnitのバージョンによって微妙に異なるのでPHPUnitの公式ドキュメントを一度確認した方が良いと思います。

CakePHP2.3からはPHP5.4のサーバ機能が動くのでそれを絡めるとカバレッジレポートが取りやすくなるのかな?などと思ったりしておりますが。。。

mink-extensionのi18nステップファイルの置き換えを可能に

Behat&MinkをBddPluginから利用する場合、シナリオに「前提 “トップページ” を表示している」のように書けたりするわけですが、これらはすべてmink-extensionのi18n機構で翻訳されています。例えばこのステップは

        <trans-unit id="i-go-to-page">
            <source><![CDATA[/^(?:|I )go to "(?P<page>[^"]+)"$/]]></source>
            <target><![CDATA[/^(?:|ユーザーが )"(?P<page>[^s]+)" へ移動する$/]]></target>
        </trans-unit>

というxmlで変換定義されているのですが、見て分かる通り正規表現です。PHPで正規表現、それに日本語というと、まぁ考慮しないといけませんね。pregmatch にUTF-8オプションを付けないと「s」が変なところでマッチして期待どおりに動きません。@kaz29さんから指摘を受けたのは「"管理者画面“へ移動する」みたいに「者」が入っているとステップが認識されないというものでした。

現時点 mink-extension へ Pull Request しているので、そのうち新しいmink-extensionのバージョンで有効になると思います。

Mink-ExtensionはComposerで依存関係を解決しているので、そこのバージョンが新しくなるまでしばらく時間がかかると思います。

それまでの対策(もしくは別の言語ファイルを入れたい場合など)として、/features/steps の下に i18n ディレクトリを作成することで、そのディレクトリを翻訳ファイルの置き場にすることができます。

上記の問題で対応版のxliffを入れたい場合、必要なファイル(例えば ja.xliff )を features/steps/i18n/ja.xliff といったパスになるように入れておいてください。これは差分インストールではなく置き換えになりますので、必要な言語ファイルはすべて配置する必要があります。

さいごに

このように、地道に修正も行っていますので、たまにGithubのリポジトリを訪れてもらえればと思います。

もしBddPluginを使ってみたいけど、よくわからない!!という場合は、twitterなどで@sizuhikoにメンションもらえればと思います。時間を調整してお会いしたり、もくもく会や勉強会のようなイベントを企画したりします。

うまく動かない!!というケースは、Githubのissueに書いてもらえると助かります。もちろん日本語でOKです。

これBehatやSpec4PHPの問題っぽいよね、と思ってもOKです。どしどしご意見&ご感想をください。

ボクは連携部分を作っただけですが、BddPluginのまとまりとしては中々良いものだと思いますので(手前味噌ですが)、少しでも多くの方にご体験いただければと思っています。

PHP Matsuri2012を終えて 〜 Retrospective of PHP Matsuri 2012 in Fukuoka

<!– more –>
On this year, PHP Matsuri had convened of 3rd time around.
The participant who first timer and the repeater are split 50:50.
It become common composition of the participant in the event of 100 person scale.

As a motive of participation are followings:
1. Repeater brings the friend of the first participation.
2. Participant in the vicinity who saw last year’s article
3. Enthusiastic fan

I think that PHP Matsuri has become a annual event at late autumn.

As the PHP Matsuri staff, this maybe first post. So I stop it only in an easy summary.
To be continues and the details are posted by DANCHO.

PHP Matsuriも3回目の開催となりました。
参加者は、始めての人が半分ぐらい、リピーターが半分ぐらいで、100人規模のイベントでは良くある人数構成になってきました。
参加の動機としては、
1. リピーターの人が初参加の友人を連れてくる
2. 去年の記事を見た、近隣の参加者
3. 熱狂的ファン
があると思っています。

そういう意味でPHP Matsuriは晩秋の恒例行事となってきたのではないかと思います。

PHP Matsuri 青年団の最初をきって書くので、簡単なまとめだけに留めたいと思います。詳しくは団長が…..

Thanks:


It had been offered by many sponsors from the enterprise to the individual.
As a result, a very gorgeous prize was able to be prepared. And, it became a very happy event.
I’m really thankful.

感謝:
今年は、企業から個人まで多くのスポンサー様にご提供いただき、大変豪華な賞品を用意でき、とても楽しいイベントになりました。本当に感謝しております。

This year’s challenge:

The 1st time and the 2nd times were the same hotel groups.
It is easy to have done arrangements and the preparation about the hall.
However, this year of the third times was a hall of “Fukuoka Prefecture Ruby and contents industry promotion center” that a public organization manages .
(The hotel prepared it until last year. )Because the task had increased, the staff became busy.
However, the registration fee became low price, and the prize of LT became gorgeous.

今年のチャレンジ:
1回目、2回目とも同じ系列のホテルだったこともあり、会場に関する手配や準備はとてもやりやすかったです。3回目の今年は「福岡県Ruby・コンテンツ産業振興センター」という公的な機関が運営する会場でした。ホテルの人が担当してくれていた雑務をスタッフ主導でやることは大変でしたが、その分参加費が安くなったりLTの賞品が豪華になったことで参加者へ還元できたのではないかと思っています。

This is good:


It is some the improved points that it is a few problem until last year
1. The wifi doesn’t disconnect. We think that we solved the problem of the network because we have controlled the connection with 3 access point.
2. Just good temperature.
There were some comments that it was cold, too. However, I think the improvement to have been done since last year (Because the hall is too wide and air-conditioning was not effective).
3. Neither the conference nor the workshop get entangled. it became a concentrating workshop by using another conference room.

特に良かった事:
昨年までちょっと課題だと思っていた部分で、いくつか改善された点ですが
1. 無線が切れない。うまく分散することで、ネットワークの問題はなかったと思います
2. ちょうど良い温度。若干寒いというご意見もありましたが、昨年は会場が広すぎて空調が効かなかったりしたので、改善はされていたと思います
3. 講演とワークショップが混線しない。会議室が別だったこともあり、ワークショップを集中して開催できたと思います

Important things :


These are important thing in the event. I think that I was able to do this this year.
1. We are nice smile
2. It doesn’t become loneliness. If first timer that there was not a friend either, then I think that they were able to exchange it at the ice break and break-time.
3. Meal is delicious.
4. Wanna become repeater. I think this to be feelings that each one was able to take home.

イベントで大事な事:
私がイベント大事だと思っていることで、今年も実践できていたと思います
1. みんな笑顔
2. ボッチを作らない。もちろん始めての参加で友人もいなかったという人もいると思いますが、アイスブレークや食事時間に交流できたと思います
3. 食事が美味しい
4. また参加したくなる。これは参加者皆さんが持ち帰っていただけた気持ちと期待しています

At last, My presentation:


In this year, I had plan to make demonstrate application look like the micro framework with using followings component:

The demonstration was not complete.
When the sample application program became runnable a little more well, I want to announce in Blog or the PHP study meet up.

最後に私事:
今年は以下のどちらかを使って、マイクロフレームワーク的なコードが書けるところをデモする予定でした。

で、結局 Respectのバグっぽい箇所をふんでしまって、朝方6時ぐらいに Rails Routerに切り替えたけど、こっちはこっちでうまく動作しなくて、8時ぐらいに元鞘に。うまく動いている箇所だけで発表しました。
これらは、もうちょっとうまく動くサンプルアプリが作れたら、BlogやPHP勉強会などで発表したいと思います。

See you PHP Matsuri 2013 (maybe HOKKAIDO) !!

また来年のPHP Matsuri(たぶん北海道)で会いましょう

CakePHP2 実践入門 が出ます

<!– more –>

表題のとおりCakePHP2.x系をターゲットにした本がいよいよ9/29に発売となります(池袋ジュンク堂では本日より10冊の先行発売があった模様です)。

本書のほとんどを企画の安藤さんが書いたのですが、わたしも共著のメンバーとして10章 プラグイン、11章 コンソール、12章 ユニットテスト を担当しました。

本書のオススメ・ポイント

執筆の一員という立場で手前味噌なのですが、まだポチっとしていない方の背中を押すという意味で、5つ紹介します。

  • 豪華な執筆陣
  • 最新のバージョン2.2への追従
  • よりチェックされた本文とサンプルコード
  • 便利なチートシート付き
  • 隠れポイント

豪華な執筆陣

本の帯にも書かれている「コミュニティ主要メンバーが最前線のノウハウを凝縮」という部分に現れているとおり、著者それぞれはCakePHPの勉強会や、翻訳、本体へのパッチ送付、Blogなどで勢力的に活動しており、得意な分野について担当するという意味では共著のメリットを最大限に活かした本と言えます。

そういう意味で各章には、これまでのCakePHP本にはなかったTipsが含まれていると思います。「実践入門」というタイトルですが、現在CakePHPを使っている方にも何か新しい気付きがあると良いなと思っています。

最新のバージョン2.2への追従

本書の始まりはCakePHP2.0の頃であり、「もしかしたら発売する頃には2.1になっているかもね」と言われていたものです。それが気付くと2.1はリリースになり、まだ2.2はリリースとは遠いところにある存在でした。2.1に対応した内容がほぼFixしたころに、2.2が驚く程の速さでリリース(ほんとの直前には2.2.1)となりさらに追従というようなサイクルでした。もちろん本書を手に取っていただけるとわかるのですが、数カ所「このバージョンでは・・・」という記述が見られるようにバージョン違いによる説明が含まれているのも特徴です。

よりチェックされた本文とサンプルコード

今まで何度か執筆に参加したのですが、今回は期間も内容もかなりタフなものだったと思います。それはバージョン追従の問題だったり、なるべく多くの内容を詰め込みたいがページ数には上限があり内容を精査したりといったことです。そんな中でも技評の担当者の方が、本の全体を通しての統一感といったところに細かくチェックしてくれていて、共著にありがちなバラバラ感が少なく通して読んだときの違和感もないと思います。また最新バージョンでサンプルコードの動作確認を都度してくれた(これは今までにない経験)ので、サンプルコードを実際に動かして試してみたいという人にも、ここ動かないなぁという迷いが少ないと思います(もちろん著者もチェックしているつもりなのですが、内容の改変などにコード部分が追いついていないということもまま・・・汗)。

便利なチートシート

巻末の30ページに渡り記述されているチートシートは本当に便利で、これは現在CakePHPで開発している人にもとても役立つものになっていると思います。これだけでも本書を手元に置く価値があるかも?!

隠れポイント

最後にお気に入りポイントを1つ。

本のカバーを外すと裏表紙にCakePHPの公式マークがプリントされています。ぜひチェックしてみてください。

さいごに

さて執筆陣も6名となり、本のサインをコンプリートしたいと思う人にはかなり難しい状況と思います。ところが、それが叶う可能性が11/3,11/4にありますよ(という宣伝)。

11/3,11/4にPHP Matsuri 2012が福岡にて開催されます。

http://www.phpmatsuri.net/2012/

このイベントの青年団がページの下にアイコン表示されていますが、なんと著者6名全員が青年団!!

つまりMatsuriに参加するとサインがコンプリートすると思いますので、今すぐサイトからチケットを申し込みましょう(数に限りがあります)。個人スポンサーもお待ちしています。