new takyam();

Qiitaぽい話はQiitaに書いていくことにする気がする http://qiita.com/takyam

Laravel 4 で使われてる Facade(ファサード) が素晴らしい件

昨日に引き続きLaravel4を調べてます。

Laravel Testing Decoded (JP)
待望のLaravel 4ユニットテスト解説本
https://leanpub.com/laravel-testing-decoded-japanese
著:JeffreyWay 訳:Hirohisa Kawase (id:HiroKws氏)

上記書籍を読んでます。

とりあえず実践してるってよりは読んでるだけなのですが、表題の通りファサードが素晴らしかったのでご紹介。

ファサードはLaravelが創りだした概念というわけではなく、GoFデザインパターン一つのようです。GoFデザインパターンに疎い自分が憎らしいです。

wikipedia

Facade(ファサード)とは「建物の正面」を意味する。異なるサブシステムを単純な操作だけを持ったFacadeクラスで結び、サブシステム間の独立性を高める事を目的とする。 引用: wikipedia Facade パターン

日本語ドキュメント

ファサードはアプリケーションのIoCコンテナに用意したクラスに「静的」なインターフェイスを提供してくれます。 引用: Laravel4 日本語ドキュメント

正直これ読んだだけだと「何かstaticなやつ作る時に何か良いヤツ」くらいの理解しか得てなかったのですが、書籍を読んで納得。

<?php
class Hoge {
    //渡された文字列をtrimしてpiyoプロパティにセットする
    public function setPiyo($piyo) {
        $this->piyo = Fuga::trim($piyo); //Fuga::trim()は文字列をtrimします
    }
}

みたいなクラスがあったとして、setPiyo()のテストをする場合、何も考えないと以下みたいになります。

<?php
class HogeTest extends TestCase{
    public function testHogeクラスのpiyoプロパティにtrimしてセットされる(){
        $hoge = new Hoge;
        $hoge->setPiyo('  ABC  ');
        $this->assertSame('ABC', $hoge->piyo);
    }
}

しかしながら、この状態は、HogeクラスがFugaクラスに依存してるわけです(メソッド中でFuga::trim()を利用している)。
これがstaticメソッドでなければ、HogeクラスにFugaクラスのインスタンスを渡してあげる事で解決する事ができますが、staticメソッドなのでそれができません。

# Fugaがstaticじゃない場合のやり方
<?php
class Hoge {
    public function __construct(Fuga $fuga){
        $this->fuga = $fuga;
    }
    public function setPiyo($piyo){
        $this->piyo = $this->fuga->trim($piyo);
    }
}

class HogeTest extends TestCase{
    public function testHogeクラスのpiyoプロパティにtrimしてセットされる(){
        //Fugaクラスのモックを作成
        $fuga = Mockery::mock('Fuga');
        $fuga->shouldReceive('trim')->once()->andReturn('trimmed string');

        //モックを渡してHogeのインスタンスを生成してテスト
        $hoge = new Hoge($fuga);
        $hoge->setPiyo('  piyopiyo  ');
        $this->assertSame('trimmed string', $hoge->piyo);
    }
}

実際に動かしてみて無いので、動くかどうかはわかりませんが、兎にも角にも「new」できる依存先の場合は、このようにモックを利用する事で、テスト時に依存関係を排除する事が非常に容易なわけです。

ただ、staticとなるとこうはいきません。FuelPHPのテストが難しい理由も、staticなメソッドが多用されている点につきます。特定のテストを実施する時だけ、Fuga::trim()の挙動を変える、という事ができないからです。

そこで、ファサードです。

FugaクラスがFacadeクラスを継承して作られてるとします。そうする事で、モックで置き換える事ができるようになるわけです。
先ほどの例でいうと、以下のようにテストできるわけです。

<?php
class Hoge {
    public function setPiyo($piyo){
        $this->piyo = Fuga::trim($piyo);
    }
}

class HogeTest extends TestCase{
    public function testHogeクラスのpiyoプロパティにtrimしてセットされる(){
        //Fugaクラスのモックを作成
        Fuga::shouldReceive('trim')->once()->andReturn('trimmed string');

        //モックを渡してHogeのインスタンスを生成してテスト
        $hoge = new Hoge();
        $hoge->setPiyo('  piyopiyo  ');
        $this->assertSame('trimmed string', $hoge->piyo);
    }
}

Wao!! Beautiful!!!!!

個人的には結構衝撃的に凄いことだと思うのですがどうでしょうか。超楽ちん。

これが可能なのも、Facadeを継承したクラスは実際にはインスタンスが生成されており、staticメソッド風なnon-staticなメソッドだからなんですね。

なので、Facadeを継承したクラスを作るには、ただクラスを作るだけじゃなくて、その後アプリに「これファサードクラスだにょーん」って登録したりと、ちょっとした手間がかかります。

が、それだけで、こんなにもstaticメソッドを含むテストが書きやすくなるなら何の問題もありません!素晴らしい!グレイト!!

というわけで、感動したのでペタペタ書いてみました。