Technote

by sizuhiko

Hello Middleman!

my.opera.com の終了に伴い、ブログを移転しました。過去のコンテンツは移行していますが、リンク切れなど起きているかもしれませんので、もし見つけましたら github の issue ページ にてお知らせいただければ幸いです。

このブログは Middleman にて構築しています。Middlemanの導入に関する記事もアップしたいと思っていますので、興味ある方はしばらくお待ちください。

2013年ふりかえり

<!– more –>この投稿は今年参加/発表したり、運営に携わったイベント、やっていた仕事の内容などについてふりかえるポストです。

1月

2013年最初のイベント参加は1/6に開催された「新春!!HTML5KARUTA大会」。 HTML5カルタとは

2012年現在、W3Cで正式に採用されているHTML5のタグは108つあります。HTML5KARUTAは、このHTML5のタグを日本古来から伝わる伝統的な遊戯、カルタとして遊びながら覚えようというものです。

というもので、当日はCakePHPユーザの集う茅場町会場(コワーキングスペース茅場町 Co-Edo)に参加しました。 みんなでビールや日本酒飲みながら楽しくカルタをやって、なんと茅場町会場で優勝いたしました。その後、第二版として販売されたHTML5カルタ初級、中級、上級を購入したのは言うまでもありません。とても楽しいので、2014年の年初めに皆様もカルタで遊んでみてはいかがでしょうか?何?お手元に無い?、大丈夫です、茅場町Co-Edoにカレンダーが揃っていますので、ドロップイン料金だけで楽しめますよ!!

続いては1/21に開催された「CakePHPもくもく会」に参加。こちらも引き続き茅場町Co-Edoでの開催です。元々は@kaz_29さんとCakePHPのBehat連携周辺のアレやコレやを解決したくて開催したのですが、コアデベロッパのGraham含め10名程が参加するイベントとなりました。そこでの成果はCakePHPのBddプラグイン v0.9.2 としてリリースしました。詳しくはこちらの記事で。

また今年はPHP Matsuriが7月開催だったので、この頃から開催会場の選定などが始まっていました。

2月

2/14に開催された「第一回CakeBeerTalk」に参加しました。こちらも茅場町Co-Edo。CakePHP1から2へのマイグレーションの話があったり、CakePHP2.3の新機能の話をしていたんですねー。気付くと年末にはもう3の話をしているというサイクルに驚きます。1月に実施したもくもく会の成果なども含め@kaz_29さんが「CakePHP2+BDD Plugin」の発表をしていました。自分が作ったプラグインを他の人が利用して発表してくれるというのはとても嬉しい事です。

続いて2/28に株式会社 Engine Yardで開催された「第65回 PHP勉強会@東京」に参加。この頃BddプラグインにありがたいPull RequestをいただきComposer対応を進めていたのですが、その過程でいわゆるForkプロジェクトを登録して良いのか?問題に直面したので若干愚痴っぽいLT「composer/installers」もしました。今も結局repositoriesに追記するようにしているのですが、どうしたら良いのかの結論は出ていない感じです。で、それについてまとめた記事はこちら。 また、スライドにも書いたのですが、CakePHP2実践入門増刷されたのも2月の事でした。

PHP Matsuriは会場が札幌全日空ホテルでほぼ確定したところでした。PHP MatusriのページをGitHubページで作り始めたのもこの時期です。

3月

3/9に開催された「(CakePHPとか)PHPのテストについての勉強会」で「テストの基本からCakePHPでTDD/BDDへ」講演しました。他の方の発表も大変に興味深く楽しいイベントでした。

続いて3/27に株式会社 Engine Yardで開催された「第66回 PHP勉強会」に参加。「ClientSide MVC Frameworks using?」というタイトルでJavaScript MVCフレームワークである「JavaScript MVC」というフレームワークについてLTをしました。名前が紛らわしいんですよね。当時ちょうど仕事でHTML5アプリというかクライアントサイドのお仕事でこのフレームワークを使っていて、とても良かったので紹介したという流れです。この後も何度か同様の発表をしたのですが、時代の流れがクライアントソフト、クライアントサーバ、シンクライアントやサーバサイドWeb、AjaxやHTML5アプリ(クライアントサーバ)といったように目紛しく変わって行く中、来年の動向も気になるところです。

PHP Matsuriはスポンサー依頼が本格的に始まったり、Facebookにイベントページが立ったり、先だって作っていた公式サイトも公開したり、いよいよ本格的に動き始めた感じでした。

4月

もちろん4月といえば4q!カンファレンス(@かもすや酒店)。今年も参加しましたー。ちょうどSakenote(#SakeLover)がリリースされた後ということで@kuranukiさんも含め盛り上がったイベント(飲み会)でした。

4/27は「CandyCane 開発会 2013春」に参加。Bddプラグインでテストを書こう!と挑んだのですが、まずモロモロ環境を作っているうちに時間切れとなってしまいました。今思えばこの時にVagrant使っていれば….。その後私が作ったものをベースにComposer対応が完了した模様です。ありがたやー。

仕事はどんどん忙しくなるし、オレ大丈夫かーというような時期でした。マジで。ちょっとイベントに行ける雰囲気でなくなってきたのがちょうどこの頃かなー。

PHP Matsuriの基調講演に Vagrant の Hashimoto さんが決まったのも4月でした。個人的には、これがキッカケで Vagrant 熱が盛り上がってきたのですよね。

5月

仕事がテンパっていてそうとうやばい状況で、どこにも行けませんでした。

5/29にPHP Matsuriのチケット販売を開始したのですが、そのサイト更新だけは気力を振り絞って対応した記憶が….

6月

というのも5/30までで。5/31から前乗りして6/1に開催されたPHPカンファレンス関西に参加。仕事はちゃんと5/30までに何とかしました。こちらでは「Newtype of Web Application Architecture」というタイトルでJavaScript MVCのLTをしました。今年2度目のJavaScriptMVC発表でした。仕事から解放されて大阪を満喫したのは言うまでもありません。無限もやしが正式なメニュー名になっていたのを覚えています(それかよw)。

もちろん6月といえば恒例の Interop にも参加。新しい話題としては特になかったけど、SDNがしっかりとしてきたのは昨年以上に感じたところでした。

PHP MatsuriはPHPカンファレンス関西のLTでチケット販売やゲストの告知をしたり、ノベルティのデザインが決まったり、LT大会用のシステムが出来上がったり、いよいよ来月の開催に向けて最後の追い込みという感じでした。

7月

いよいよPHP Matsuri2013 Sapporo開催。私は夏休み併用で7/10から移動を開始(Matsuriは14,15日)。初の北海道上陸を自家用車で成し遂げようということで大洗からフェリーで行きました。で、その旅の写真はこちら。日焼けして真っ黒な顔で会場入りした私を見て「リゾートを満喫してきた人がいる」と言われました。まぁ確かにMatsuriの写真を見るとサングラス日焼け痕がクッキリとw。

PHP Matsuriでは司会と最初の講演「PHP x Agile 〜プログラマ視点から気にしたい事」を担当。スライドだけ見るとそれを薦めているように見える箇所もありますが、反対の意味として話した内容もあるので単語だけ一人歩きしないと良いなとおもっています。

なおハッカソンでは、JavaScriptMVCとCakePHPで作るプロトタイプ開発(9月のPHPカンファレンスの発表資料を参照してください)という発表をしたのですが、ネタが被りまくりな上、TwitterBootstrapのモーダルボタンがプロジェクタの投影範囲外というトラブルに見舞われ失敗しました….

7/22に株式会社 Engine Yardで開催された「第70回 PHP勉強会@東京」に参加。

また、月末の30日には元CakePHPのリードデベロッパだったNateさんが来日したのに合わせたイベント「緊急開催 Lithium Tokyo 〜Nate Abele氏来日イベント〜」に参加しました(@yandoさんのイベントレポート)。@koriymさんとNateの熱いリソース指向の話はとても素晴らしかったです。

この頃、社内で「インターフェースデザインの実践教室」の読書会を立ち上げました。途中まで進んだところで社外作業になってしまい中断状況。はやく再開したい…

8月

CakeFest2013まであともう少しという頃、CakeFestの発表者にも名前を連ねるRoland氏がオーストリアから来日してBanchaの講演をするというので「CakePHP Tokyo 〜 CakePHPとSenchaTouchによる超高速モバイル開発〜」に参加しました。昨年はJQueryMobileを使った仕事をしたり、今年はJavaScriptMVCを使ったりと、似たような開発をしてきた中でSenchaTouchとどういう連携をするのかとても興味があったのですが、SenchaTouchというよりもそのSDKを使ったパワフルな開発サポート機能もある点がとても面白かったです。

そして30日からCakeFestへ〜

9月

5月前後のちょうど多忙な時期にCall For Paperがあったので、結局今年はスピーカー申し込みはできず一般参加です。でも当日LT募集もあるし資料だけは仕込んで参加。今年もくもく会で仕上げたBDDプラグインのアップデート内容などを発表しよう…と思っていたらペチャクチャ枠に移動となって、挙げ句ペチャクチャ枠の申し込み者が少ないからキャンセル扱いにwww。まぁその後行われたくじ引き大会?で一等の賞品をもらったので、終わりよければ全てよしということで。

CakeFestでの内容は@yandoさんのレポートに、私と@yandoさんが現地から放送したCakeRadioGagaの動画がアップされているので、そちらをご覧いただければと思います。(そういえばかなりメモ取ったのに何もまとめ記事にしていなかった)。 この放送の後は、残っていた他の参加者と焼肉を食べに行ったのでした。

9/14にはPHPカンファレンスが開催され「15分で出来るPHPとJavaScript MVCフレームワークで作るプロトタイプ」というタイトルで講演してきました。今年4回目のJavaScriptMVC関連(厳密にはこの時期にはJavaScriptMVCがCanJSベースの新しいバージョンになっていたので、内容には変化がありました)の発表でした。AngularJSの勢力は増す一方ですが、CanJS(ならびにJavaScriptMVC)も頑張るぞ。

またこの頃からCakePHP関連のお仕事を手伝うようになったのですが、現場のControllerのテストがあまりにアレな感じでしたので「CakePHPのコントローラテストで注意すること」という記事にしてまとめました。また仕事で本格的にVagrantを使ったのもこのお手伝いが最初となりました(これまでは個人の開発環境ばかり)。

10月

バッグをPROTEXのアタッシュケースに変えました。というかしばらく使っていたトートバッグは個人的に合わず….

新しいCakePHPプラグインとしてRailsではおなじみのFabricatorのクローンを作りました。GithubそのときのTweetブログ記事。Fabricatorというより作ってみたらFactoryGirlの機能も盛り込みたくて、結局二個一みたいな感じになっていますが、まぁそこは….

現場であまりにFixture絡みでテストが失敗するので、そんなんじゃダメだーという訳で(およそ30分ぐらいでお昼食べた後すぐ)作ったのです。

11月

Fabricateにsequence機能などいくつかRuby側にある機能を追加しました。ブログ記事

11/29に株式会社 Engine Yardで開催された「第73回 PHP勉強会@東京」に参加。FabricatorとFakerを使った「テストデータの作り方」という発表をしました。

その翌日11/30には「HTML5カンファレンス」にボランティアスタッフとして参加。昨年に続く参加だったのですが、より規模が大きくなり発表も多様でした。動画がアップされているので、すべてのセッションを閲覧できるようになっています。

12月

クリスマスにケーキを食べながらCakePHPの話をしよう、という事でCakePHPのイベントが開催される事に。運営でお手伝いしてきました。 「CakePHPクリスマス勉強会」。LTに空きがあるという事で先月のPHP勉強会と同じネタでしたが「テストデータの作り方」について再演しました。

お仕事では引き続きCakePHPを使った開発現場の支援をしております。

まとめ

ということで、5月危機を除いては月に一度は何かしらの発表、発信はできていたようです。

もっと頑張っている人もいっぱい知っていますが、自分のペースとしてはこのぐらいかなー、後は書籍の話もいただいておりますので、いろいろゆっくりと。

とはいえBDDプラグインのBehat3対応と、Fabricateの拡張も(なるはやで)やりたいなー。という来年に希望を持った前向きな感じで今年を〆たいと思います。

そうそう、3月にはこのブログを書いている My Operaがサービス終了となるので、どこかに移動しなくちゃいけない。実はそれが最優先だったりするのですが(汗

ということで何だかバタバタした終わり方ですが、来年も各所で皆様に会えるのを楽しみに。Co-EdoからPOST。

CakePHPのデータジェネレータFabricateにsequenceなどの機能を追加しました

<!– more –>先日カッとなって作った Fabricate ですが、その後実運用していくうちにいくつかの機能でRuby本家に近づけたくなったので実装を追加しました。

GitHub: sizuhiko/Fabricate

変更概要

  • closure だけでなく、array 形式での属性値変更が可能に
  • 初期連番をconfigで指定可能に
  • sequenceメソッドで柔軟な採番が可能に

array形式での属性値変更

これまで属性値を自動生成でなく、自分で決めた値にしたい場合は、無名関数(closure)でarrayを戻す必要がありました。
これを直接引数でarrayを指定できるようにしました。

従来の記述方法:

Fabricate::create('Post', 10, function($data){
    return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"];
});

新しくサポートした記述方法:

Fabricate::create('Post', 10, ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]);
});

もちろん、第二引数の生成レコード数を省略して1レコードだけ作ることも可能です。

$dataの内容見ないのに、とか全部固定なので無名関数じゃなくても良い等のシーンが思いのほか多いもので。

初期連番をconfigで指定

これまで連番(スキーマで数値カラムになっている項目は自動的に連番対象になる)は1から常に始まるようにしていたのですが、こちらもやはり変更したい場合があって、本家同様にconfigで変更できるようにしました。

Fabricate::config(function($config) {
    $config->sequence_start = 100;
});

config関数は、環境定義オブジェクトのインスタンスを引数に取るメソッドを呼び出すので、その中で public 属性値である sequence_start の値を変更してください。
config関数を使わない場合は、従来通り1から開始されます。

sequenceメソッドで柔軟な採番

これが今回のバージョンアップで最も対応したかった機能で、連番を項目毎に変更したり、独自フォーマットに変更できたりします。
この機能に対応するために、attributes_for(), build(), create() の各メソッドで属性値を上書きできる無名関数に第二引数を追加しました。

Fabricate::attributes_for('Post', 10, function($data, $world){
    return [
        'id'=> $world->sequence('id'),
        'title'=> $world->sequence('title', 1, function($i){ return "Title {$i}"; })
    ];
});

上記のように、$world を引数に取るようになりました。もちろん利用しない場合は省略可能です。
$worldは新しく追加したクラス FabricateContext のインスタンスで sequence メソッドを持っています。

sequenceメソッドは、3つの利用方法に対応しています。

  • configで設定された開始値(または1)から開始する連番にする:

    $world->sequence(‘id’)

  • 開始番号を指定して連番にする:

    $world->sequence(‘id’, 10) // この例では10から開始する

  • 独自形式のユニーク文字列を作成する:

    $world->sequence(‘title’, function($i){ return “Title {$i}”; } // または開始番号を指定して $world->sequence(‘title’, 1, function($i){ return “Title {$i}”; }

一番上の利用方法は、DBのスキーマ定義が数値である限りは、sequenceメソッドを使わなくても同じ連番となるので、あまり利用ケースはないと推測されます。

本家のFabricator では同一プロセス中でのSequenceは常にユニークな値になるようになっていますが、CakePHPプラグイン版ではデータ生成のattributes_for(), build(), create() 各メソッドの呼び出し単位でユニークになります。
つまり呼び出し毎に開始番号から採番されます。

もし要望があれば、プロセス単位に連番を管理することも可能なので、Githubのissueなどで連絡ください(日本語でok)。
おそらく対応としては config で定義でできるようにすると思います…(併用でなく、プロセスか生成毎かを選択するイメージ)

CakePHPのデータジェネレータ Fabricate を作りました

<!– more –>Fabricate は Ruby の Fabricator に影響を受けたもので、データを簡単にすばやく生成する CakePHP2 用のプラグインです。

GitHub: sizuhiko/Fabricate

動作確認しているバージョンはCakePHP2.3.10, CakePHP2.4.1ですが、他のバージョンでもCakePHP2系なら動くと思います。

インストール方法

Composer対応しているので、composer.json に追加するだけで大丈夫です。テストだけに利用するなら require-dev に、実行コードからも使いたい場合は require に追加してください。どちらでも動作します。

    "extra": {
        "installer-paths": {
            "Plugin/Fabricate": ["sizuhiko/fabricate"]
        }
    },
    "require": {
        "composer/installers": "*",
        "sizuhiko/fabricate": "*"
    }

composer/installers に対応していますので、お好みで extra/installer-paths に追加すれば、Plugin の下に良い感じでインストール可能です。

後は bootstrap.php でプラグインの宣言を追加してください。

CakePlugin::load('Fabricate');

概要

Fabricateプラグインには3つのメソッドがあります。

  • Fabricate::attributesfor(:modelname, :numberofgeneration, :callback) : CakePHPの属性配列をデータ付きで返します(複数件生成可能)
  • Fabricate::build(:model_name, :callback) : CakePHPのモデルインスタンスを返します(1件のみ生成可能)
  • Fabricate::create(:modelname, :numberof_generation, :callback) : 指定された件数分データベースに生成する

用途

このプラグインは以下のような用途にとても向いています。

  • ページングのテストをするのに大量にデータを生成したい
  • fixture だと各テストケースで同じデータが生成されてしまうけど、各テストケース毎に違うデータを生成したい。かつテスト対象のカラム以外は適当な値が自動的に入って欲しい
  • Bddプラグイン(CakePHPのBehatインテグレーションプラグイン)のステップで「Postモデルに100件のデータが登録されていること」みたいなのを書きたいのに、簡単に記述できないなぁと思ったとき

使い方&サンプル

attributes_for

$results = Fabricate::attributes_for('Post', 10, function($data){
    return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"];
});

// $results is followings :
array (
  0 => 
  array (
    'id' => 1,
    'title' => 'Lorem ipsum dolor sit amet',
    'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.',
    'created' => '2013-10-09 12:40:28',
    'updated' => '2013-10-09 12:40:28',
  ),
  1 => 
  array (
  ....

これでPostモデル(第一引数に名前を指定)のスキーマ情報からカラム(属性)を取得して、タイプに応じた値が自動的に生成されて配列で戻ります。
第二引数に生成するレコード件数を指定します。
ただ、全部自動的だと困るという場合があるので、3つ目の引数にコールバック関数を指定可能になっています。このコールバック関数は、10件のデータを生成する場合、都度計10回呼び出されます。
戻り値として、属性名をキーに値を持つ連想配列を指定すると、都度array_mergeが実行されます(recursiveではありません)。
データの自動生成処理は、CakePHPのコアにあった FixtureTask の _generateRecords() というメソッドを拝借してきて、そのまま利用しています。
CakePHPでfixtureを生成したときrecordsに入っている内容と同じ物になります。

このメソッドの使いどころとしては、関連先との関連も生成して saveAll したいといったような、ちょっと応用的な使い方に向いていると思います。

また、第二引数、第三引数は省略可能ですので、

// 1件だけ生成
Fabricate::attributes_for('Post');
// 1件だけコールバックを指定して生成
Fabricate::attributes_for('Post', function($data){
    return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"];
});
// 10件生成
Fabricate::attributes_for('Post', 10);

というバリエーションでも利用可能です。

build

とりあえず本家のFabricatorに近づけたたくて作ったメソッドですが、CakePHPではモデルのインスタンスを複数同時に持てないので、正直使い勝手としては微妙です(作者が言うなという話ですが)。

create

最も簡単にデータをデータベースに保存するなら、このメソッドで十分です。
attributesforで作ったレコードをすべてデータベースに保存します。
引数の使い方はattributes
forと同一になっています。

きっかけと今後

ページングのテストを書こうとして、とにかく面倒になってきて「何でCakePHPにはFabricatorがないんだー」というノリで、カッとなって作った系ですので、機能的には最低限になっています。
かつたぶん最新のFabricatorのクローンとも言い難い状況です。

またカッとなったら、最新のFabricatorの機能に近づけようかな〜、どなかた使っていただける方がいればご要望、機能追加は GitHubのIssue、Pull Requestでお願いします。

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など)やテストケースのテストコードを見ると、これまでにテストの書き方がわからなかったところも、腑に落ちることがあるはずです。