2012-03-12 7 views
3

symfonyの2とユニットテスト:私はモックオブジェクトを注入することはできませんので、しかし、これは、ユニットテストのためにスケールしないPropelORM、私はこのように書くのが習慣に慣れて

$results = SomeModelQuery::create()->filterByFoo('bar')->find(); 

を、すなわち私ができます返されるデータには影響しません。フィクスチャのデータを使用したいのですが、できません。

また、オブジェクトを注入する偉大なようです:

class Foo 
{ 
    public __construct($someModelQuery) 
    { 
     $this->someModelQuery = $someMOdelQuery; 
    } 

    public function doSthing() 
    { 
     $results = $this->someModelQuery->filterByFoo('bar')->find(); 
    } 
} 

DIは恐ろしい感じています。私は模擬してスローするために数十のクエリオブジェクトを持っています。コンストラクタを通した設定は、醜くて苦痛です。メソッドを使った設定は、呼び出し時に忘れる可能性があるため、間違っています。そして、これらのクエリオブジェクトを手動で作成するたびに、常に個々のlibとアクションが苦痛を感じます。

PropelORMクエリクラスを使用してDIをエレガントにする方法はありますか?静的にすべてを呼び出し、依存性注入を使用して、それはあなたが探しているものかもしれ間に段差がある私の意見(およびMartin Folowers's)で

$oneQuery = OneQuery::create(); 
$anotherQuery = AnotherQuery::create(); 
// ... 10 more ... 
$foo = new Foo($oneQuery, $anotherQuery, ...); 
$foo->callSomeFunctionThatNeedsThose(); 

答えて

3

:私のようなメソッドを呼び出す必要はありません。

私は完全なDI(例えば、Zend Framework MVC)を行うことができませんが、サービスロケータを使用します。サービスレイヤは、すべてのクラスがそこから依存関係を取得するための場所になります。あなたのクラスの依存関係のための1つの深い抽象化と考えることができます。 Service Locatorの使用には多くの利点がありますが、ここではテスト容易性に焦点を当てます。

のは、いくつかのコードに取得しましょう、ここにあるそれがないすべてのメソッド「検索」が呼ばれない限り、それ自体を返しているモデルのクエリクラス

class SomeModelQuery 
{ 
    public function __call($method, $params) 
    { 
     if ($method == 'find') { 
      return 'Real Data'; 
     } 
     return $this; 
    } 
} 

です。次に、ハードコーディングされた文字列 "Real Data"を返します。

今私達のサービスロケータ:

class ServiceLocator 
{ 
    protected static $instance; 

    protected $someModelQuery; 

    public static function resetInstance() 
    { 
     static::$instance = null; 
    } 

    public static function instance() 
    { 
     if (self::$instance === null) { 
      static::$instance = new static(); 
     } 
     return static::$instance; 
    } 

    public function getSomeModelQuery() 
    { 
     if ($this->someModelQuery === null) { 
      $this->someModelQuery = new SomeModelQuery(); 
     } 
     return $this->someModelQuery; 
    } 

    public function setSomeModelQuery($someModelQuery) 
    { 
     $this->someModelQuery = $someModelQuery; 
    } 
} 

これには2つのことを行います。グローバルスコープメソッドインスタンスを提供し、いつでも取得できます。それをリセットさせることと一緒に。次に、モデルクエリオブジェクトのgetおよびsetメソッドを提供します。まだ設定されていない場合は遅延ロードを使用します。

今すぐ実際の作業を行うコード:

class Foo 
{ 
    public function doSomething() 
    { 
     return ServiceLocator::instance() 
      ->getSomeModelQuery()->filterByFoo('bar')->find(); 
    } 
} 

fooがサービスロケータを呼び出して、それはそれからクエリオブジェクトのインスタンスを取得し、それがそのクエリオブジェクト上に必要呼び出しを行います。

ここで、このすべてのための単体テストを書く必要があります。ここには:

class FooTest extends PHPUnit_Framework_TestCase 
{ 
    protected function setUp() 
    { 
     ServiceLocator::resetInstance(); 
    } 

    public function testNoMocking() 
    { 
     $foo = new Foo(); 
     $this->assertEquals('Real Data', $foo->doSomething()); 
    } 

    public function testWithMock() 
    { 
     // Create our mock with a random value 
     $rand = mt_rand(); 
     $mock = $this->getMock('SomeModelQuery'); 
     $mock->expects($this->any()) 
      ->method('__call') 
      ->will($this->onConsecutiveCalls($mock, $rand)); 
     // Place the mock in the service locator 
     ServiceLocator::instance()->setSomeModelQuery($mock); 

     // Do we get our random value back? 
     $foo = new Foo(); 
     $this->assertEquals($rand, $foo->doSomething()); 
    } 
} 

ここでは、実際のクエリコードが呼び出され、クエリコードが偽装された例を挙げました。

これにより、単体テストするクラスにすべての依存関係を注入する必要がないモックを注入することができます。

上記のコードを書くには多くの方法があります。概念の証明としてそれを使用し、あなたのニーズにそれを適応させる。

+0

他にも素晴らしいですが、私はシングルトンのファンではありません。なしでこれを行う方法はありますか?また、実際にテストデータを作成し、テストごとに削除してテストデータで再作成するオプションもありますが、それは少し遅いです。 – Tower

+0

私が言ったように、これを行う方法はたくさんあります。あなたがシングルトンを避けようとしているなら、必要な場所にService Locatorを挿入することができます。また、テスト容易化のためだけにオプション注入を行うこともできます。つまり、依存関係が設定されている場合はそれを使用し、それ以外の場合は独自のオブジェクトを作成するか、オブジェクトのグローバルインスタンスを取得します。これにより、生産コードで簡単に使用でき、テストコードにモックを挿入することができます。消費するクラスが独自のインスタンスを作成するようにサービスロケータを設定できます。しかし、真のDIのないものは、DB接続のようなシングルトンである必要があります – SamHennessy

関連する問題