前回の Java編に引き続きまして
今回も、テスト対象コードを変えずに、頑張ってテストを書くにはどうしたらいいのかを考えてみます。
// もちろん、リファクタリングをするべきですが、リファクタリングの足掛かりとして
// テストを先に書いておきたいという思いがあるのです。

PHPでも Mock というか、Test Double を楽しむための方法が幾つかありそうなのでまとめてみる。

オートロードが利用できるケース

symfony1.x の、 sfContext (Singletonパターン) に関わるテストで利用されている手段です。クラスの定義してあるファイルを自動的にロードするための機能spl_autoload_register() で、登録した関数は、すでにクラスが定義済みであった場合は呼ばれません。

これを利用して、全く同名のモッククラスを定義したファイルを用意しておき、テスト時に require_once などで、そのファイルを呼び出してやるわけです。これで、対象とするクラスの動きとしてはモッククラスの動きになります。

非常でお気軽な方法ですが、モック対象が頻繁に変更するようなケースなどではオススメできないかもしれません。
すべてのメソッドを網羅している必要はなく、騙すだけで良いならばこの方法はいいかもしれません。


テキトーなので、autoload.php は PSR-0 などに全然沿っておりませんのでご了承ください。 test1.php に関しては、オートロードで lib/Sample.php がロードされることはなく、先に定義した Sample クラスが利用されていることがわかります。

runkit

JMockit のように、メソッドの一部のみの挙動を変更したい!というニーズに答えるならば runkit になるかと思います。 php モジュールになってしまいますが、メソッドの内容を書き換えたり、定数を消したりとなかなか魔術を感じる便利なものです。 ただし、PHP5.3 では pecl からインストールができないので svn からソースコードを落とし、手動でコンパイルしてやる必要があります。

$ svn co https://svn.php.net/repository/pecl/runkit/trunk/ runkit
$ cd runkit
$ phpize
$ ./configure
$ make 
$ sudo make install 

以下は、クラスの定義を書き換えている様子です。

runkit + PHPMockFunction

runkit を利用して、かつ JMock のように使いやすくやろうじゃないか的なライブラリもあるのですが、
どうやらメンテされてません。誰かクールなのを作りませんか? 言いだしっぺですかそうですか。

PHPMockFunction

PHPUnit の Mock

PHPUnit の Mock は、テスト可能なコードを書いていけば使い物になりますが、レガシーなコードのテストをまずは書いて進みたいという時には適さない様に思えます。

ちなみに、Symfony2 のコンポーネントは、この機能を利用しているため、テストもコードも非常に洗練されているような気がします。

【追記】次回予告

@hidenorigoto 先生にツッコミを受けたので、続編を書きます。mockery はどこまで出来るかわからないですけど、ドキュメント見た限り、なかなかクールでした!(ってか初めて知りました。)

こんにちは。MA6に作品を出したよ。今年は、応募作品が500作品以上もあるらしい!すごい!!

で、僕の作品。
TabeRoulette
飯に迷った時に、ランダムに決定するための恐ろしいWebアプリです。70%くらいはネタです。
飯屋密集地帯じゃないと苦しいかもしれないです。
僕のように、優柔不断な人間が使います。

それは、さておき副産物は結構考えて作ってます。

副産知識

このアプリは symfony1.4 をフレームワークとして作られているわけです。
JavaScript のフレームワークとしては Prototype.js と script.aculo.us を使っている
ワケですが、いちいち Prototype.js やらをコピーしてバージョン管理してっていうのが
面倒なので、Google Libraries API を大活用しています。

載せてないけど、ある意味マッシュアップ。

これの利用にはAPIキーが必要なのですが、 view.yml にAPIをべた書きしてしまうのは気が引けます。

そこで、config/ProjectConfiguration.class.php
setup() で

sfConfig::set('sf_google_libraries_api_key', 'hogehoge');

などとして、view.yml では

  javascripts:
    - http://www.google.com/jsapi?key=%SF_GOOGLE_LIBRARIES_API_KEY%

としたよというお話。

なぜ、コレが出来るの??ってなるかもしれませんが、
http://www.symfony-project.org/reference/1_4/en/03-Configuration-Files-Principlesには
%XXXX% は予め定義された値に置き換えられ、setting.yml で定義された値が使える。ということになっています。setting.yml では定義しませんでしたが、sfConfigHandler は、 %XXXX% が来たときに sfConfig::has(‘xxxx’) が正なら、 sfConfig::get(‘xxxx’) に置き換えるという処理を行なっているため、sfConfig::set() をするタイミングを間違えなければ利用できます。

副産物1 – SlotColumn

このWebアプリの特徴とも言える、回るUIです。
http://jsdo.it/ooharabucyou/5oOh
誰得。

副産物2 – sfSmartphoneViewPlugin

奇跡的に、ちゃんと役に立つ可能性のあるsymfonyプラグインです。


https://github.com/kawahara/sfSmartphoneViewPlugin

スマートフォン向けテンプレートとPC向けテンプレートをスムーズに切り替える為のものです。
完全オリジナルのアイデアというわけではなく、
ウノウラボの中村さんが書いた記事「携帯とスマートフォンでsymfonyのテンプレートを切り替える 」の影響を強烈にうけています。

オリジナルの要素としては、iPhone向けとAndroid向けについて別々のテンプレートにできたり(使わなかったけど)、view.yml にスマートフォン用の設定を書く事が出来るようになることです。
そして、なによりプラグインとして用意したため、symfony(1.4)のプロジェクトに設置して、有効にした瞬間から
この機能は有効になります。

チュートリアル

独自の view クラス、 view.yml の ConfigHandler を利用していないという前提です。独自のviewクラスだったりする場合は、ちょっと弄る必要があるかもしれませんが、Apache License 2.0 で晒しておりますので、どうぞどうぞ。

また、事前に frontend というアプリが作られているという前提のお話をします。

plugins/ に sfSmartphoneViewPlugin を clone してきます

スマートフォンのテンプレート利用時は、それ専用の stylesheet と layout を使いたいので
default に stylesheets, has_layout, layout の設定行があったら削除しておきましょう。


apps/frontend/config/view.yml
に以下を追加

# スマートフォン向け設定
_smartphone:
  stylesheets: [smartphone.css]
  metas:
    viewport: width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no
  has_layout: true
  layout: smartphone

# スマートフォン以外だったときの設定
_another: 
  stylesheets: [main.css]
  has_layout: true
  layout: layout

スマートフォンだった時の設定としてある、 smartphone.css や レイアウトの smartphone.php は予めつくっておきましょう。

普通の symfony プロジェクトの場合は、 apps/*/modules/*/templates/には indexSuccess.php のようなテンプレートがあるのですが、スマートフォン向けのテンプレートを用意するときは indexSuccessSmartphone.php のようなサフィックスを付けます。
このようにすると、スマートフォンからのアクセスの場合、 indexSuccessSmartphone.php があった場合にテンプレートとして利用し、view.ymlの設定として _smartphone を事前に読みます。このため、view.yml の _smartphone に スマートフォン向けしか使わないライブラリをロードしたり meta を追加したりということが出来ます。(上の例では viewport を追加している)

テンプレートのファイルに IPhone というサフィックスを付けておくと、 iPhone/iPod からのアクセスからのときだけそれをテンプレートとして利用し、view.yml の設定として、 _i_phone および _smartphone を読みます。
Androidも同様です。
UA的には定義したスマートフォンだったけど、テンプレートファイルが無かったときは Smartphone のサフィックスがつくテンプレートファイルがあるかを確認し、あったら view.yml の _smartphone を読みます。
最終的にスマートフォンでないか、スマートフォン向けテンプレが無かった場合は view.yml の _another の設定を読みます。

今回は、 スマートフォンアクセスの場合は view.yml の _smartphone の設定は読んでもらいたいのだけど、indexSuccess.php はそのまま使いたいというシチュエーションが産まれたので、それ用の設定を用意しました。
indexSuccess のみに適用する場合は、 モジュールの view.yml で以下のような設定を追加します。

indexSuccess:
  use_smartphone_view: true

なお、パーシャルでも同様の事ができるようになってます。
スマートフォンからのアクセス時に include_partial(‘hoge’); とした場合、_hogeSmartphone.php のようなファイルがあると、それを使います。

課題としては、本当にsmartphoneというサフィックスは適当なのかってことで迷っているのと、
現状ハードコーディングで、 iPhone/iPod/Android を定義してしまっているので、
ここらへんは設定できるようにしたりしたいと思いますよ。これだけでスマートフォン対応とか言うのはあんまりなので。

とりあえず、TabeRouletteは、このプラグインがbalibali動いているので、UAをiPhoneとして偽装すれば微妙に違うコンテンツが見えるっていうことがわかると思います。

(CSS Media Queries 使えってツッコミをう受けそうだが。)