2011-10-31 6 views
14

私はPHPUnitまたはBehatテストの期間中インスタンス化されたDateTimeのすべてのインスタンスの時間を設定できるようにしたいと思います。Mockingテスト目的でDateTimeのすべてのインスタンスで使用される時間。

私は時間に関するビジネスロジックをテストしています。たとえば、クラス内のメソッドは、過去または将来のイベントのみを返します。

ことだ私は可能であればやっていけない:

1)のDateTimeのラッパーを書くと私のコード全体のDateTimeの代わりにこれを使用します。これには、私の現在のコードベースを少し書き直す必要があります。

2)テスト/スイートを実行するたびにデータセットを動的に生成します。

質問は次のとおりです。DateTimesの動作をオーバーライドして、要求されたときに特定の時間を常に提供することは可能ですか?

+0

あなたはまだ答えを受け入れませんでした。あなたは、あなたが何を求めているのか、またその答えがあなたを満足させない理由を明確にしてください。 – Gordon

+0

ちょうど同じ問題を抱えていたので、@shouzeの答えからのphp timecop拡張は魅力的だった。 –

答えて

15

テストで必要なDateTimeメソッドをスタブして、期待値を返す必要があります。

$stub = $this->getMock('DateTime'); 
    $stub->expects($this->any()) 
     ->method('theMethodYouNeedToReturnACertainValue') 
     ->will($this->returnValue('your certain value')); 

彼らはあなたのコードにハードコードされているので、あなたがメソッドをスタブすることができない場合は起動する方法について説明し

を見て、http://www.phpunit.de/manual/3.6/en/test-doubles.html

を参照してください。 newが呼び出されるたびにコールバックします。次に、DateTimeクラスを、固定の時間を持つカスタムのDateTimeクラスに置き換えることができます。別のオプションは、@Gordonは、すでに現在の時刻に依存しているテストコードの一つ、むしろハック、方法がある指摘ものにに追加http://antecedent.github.io/patchwork

+0

ありがとうゴードン - DateTimeの依存関係は、大部分のコードでハードコードされています。私はそれを原始的なものとして使っている間違いをしました。他のすべての依存関係は注入されるので、模擬するのは簡単です。コードの移植性を低下させるので、私はむしろ拡張子を使用しないでください。唯一の選択肢かもしれませんが!ご回答有難うございます。 –

2

を使用することです:

私はなるただ一つの保護方法をからかっあなたはあなたが現在の時間のようなことを尋ねることができるクラスを自分で作成する必要性の問題を回避することができる「グローバルな」価値(これはクリーナーですが、PHPでは人々がしたくないと論じる/理解できるそれ)。次のようになります

<?php 

class Calendar { 

    public function getCurrentTimeAsISO() { 
     return $this->currentTime()->format('Y-m-d H:i:s'); 
    } 

    protected function currentTime() { 
     return new DateTime(); 
    } 

} 
?> 

<?php 


class CalendarTest extends PHPUnit_Framework_TestCase { 

    public function testCurrentDate() { 
     $cal = $this->getMockBuilder('Calendar') 
      ->setMethods(array('currentTime')) 
      ->getMock(); 
     $cal->expects($this->once()) 
      ->method('currentTime') 
      ->will($this->returnValue(
       new DateTime('2011-01-01 12:00:00') 
      ) 
     ); 
     $this->assertSame(
      '2011-01-01 12:00:00', 
      $cal->getCurrentTimeAsISO() 
     ); 
    } 
} 
6

またhttps://github.com/rezzza/TimeTraveler

このPHP拡張モジュールもありますが、パッチ適用猿をルビーに似たものを持って来るためにAOPのPHP PECLの拡張子を使用して、時間旅行者のlibを使用することができます、ruby timecopからインスピレーションを受けました https://github.com/hnw/php-timecop

+0

質問の下にコメントなどのソリューションを追加してください。 – Lal

+0

私はしたいと思いますが、ATMに新しく登録する特権はありません。 – shouze

+0

最後に私が見たTimeTravelerが壊れました。 timecopの代替方法は良い方法です。 –

0

で明示的にDateTime()をインスタンス化するように実装を変更することができます:

new \DateTime("@".time()); 

これはクラスの動作を変更しません。しかし、今することができます名前空間機能を提供することによりmock time()

namespace foo; 
function time() { 
    return 123; 
} 

ます。また、そうするために私のパッケージphp-mock/php-mock-phpunitを使用することができます。私はPHPUnitのテストを使用して機能テストを実行するためにSymfony's WebTestCaseを使用していたよう

namespace foo; 

use phpmock\phpunit\PHPMock; 

class DateTimeTest extends \PHPUnit_Framework_TestCase 
{ 

    use PHPMock; 

    public function testDateTime() 
    { 
     $time = $this->getFunctionMock(__NAMESPACE__, "time"); 
     $time->expects($this->once())->willReturn(123); 

     $dateTime = new \DateTime("@".time()); 
     $this->assertEquals(123, $dateTime->getTimestamp()); 
    } 
} 
0

をバンドルすると、すぐにDateTimeクラスのすべての使用法を模擬することは現実的ではなくなりました。

私はこれを行うために見つけた最良の方法が延びて自分のDateTimeクラスを実装することである

など、それはこのようなテストクッキーやキャッシュの有効期限などの時間をかけてリクエストを処理するようアプリケーションをテストしたかったですその時点以降に作成されるすべてのDateTimeオブジェクトにデフォルトの時間スキューを追加/減算するための静的メソッドをいくつか提供します。

これは実装が非常に簡単な機能で、カスタムライブラリをインストールする必要はありません。

警告の買い手

この方法の唯一の欠点は、symfonyフレームワーク(または使用しているものは何でもフレームワーク)ですが、あなたのライブラリーを使用することはありませんので、このような内部キャッシュ/クッキーなど、それ自体を処理するために期待されます任意の行動、これらの変更によって影響を受けることはありません。これを使用する

namespace My\AppBundle\Util; 

/** 
* Class DateTime 
* 
* Allows unit-testing of DateTime dependent functions 
*/ 
class DateTime extends \DateTime 
{ 
    /** @var \DateInterval|null */ 
    private static $defaultTimeOffset; 

    public function __construct($time = 'now', \DateTimeZone $timezone = null) 
    { 
     parent::__construct($time, $timezone); 
     if (self::$defaultTimeOffset && $this->isRelativeTime($time)) { 
      $this->modify(self::$defaultTimeOffset); 
     } 
    } 

    /** 
    * Determines whether to apply the default time offset 
    * 
    * @param string $time 
    * @return bool 
    */ 
    public function isRelativeTime($time) 
    { 
     if($time === 'now') { 
      //important, otherwise we get infinite recursion 
      return true; 
     } 
     $base = new \DateTime('2000-01-01T01:01:01+00:00'); 
     $base->modify($time); 
     $test = new \DateTime('2001-01-01T01:01:01+00:00'); 
     $test->modify($time); 

     return ($base->format('c') !== $test->format('c')); 
    } 

    /** 
    * Apply a time modification to all future calls to create a DateTime instance relative to the current time 
    * This method does not have any effect on existing DateTime objects already created. 
    * 
    * @param string $modify 
    */ 
    public static function setDefaultTimeOffset($modify) 
    { 
     self::$defaultTimeOffset = $modify ?: null; 
    } 

    /** 
    * @return int the unix timestamp, number of seconds since the Epoch (Jan 1st 1970, 00:00:00) 
    */ 
    public static function getUnixTime() 
    { 
     return (int)(new self)->format('U'); 
    } 

} 

は単純です:

public class myTestClass() { 
    public function testMockingDateTimeObject() 
    { 
     echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n"; 
     echo "before: ". (new DateTime('tomorrow'))->format('c') . "\n"; 
     echo "before: ". (new DateTime())->format('c') . "\n"; 

     DateTime::setDefaultTimeOffset('+25 hours'); 

     echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n"; 
     echo "after: ". (new DateTime('tomorrow'))->format('c') . "\n"; 
     echo "after: ". (new DateTime())->format('c') . "\n"; 

     // fixed: 2016-06-18T00:00:00+00:00 <-- stayed same 
     // before: 2016-09-20T00:00:00+00:00 
     // before: 2016-09-19T11:59:17+00:00 
     // fixed: 2016-06-18T00:00:00+00:00 <-- stayed same 
     // after: 2016-09-21T01:00:00+00:00 <-- added 25 hours 
     // after: 2016-09-20T12:59:17+00:00 <-- added 25 hours 
    } 
} 
関連する問題