2011-10-31 34 views
14

我希望能夠爲PHPUnit或Behat測試的持續時間實例化每個DateTime實例設置時間。嘲笑所有DateTime實例用於測試目的的時間。

我正在測試與時間有關的業務邏輯。例如,類中的方法僅返回過去或未來的事件。

件事是我不想做,如果可能的:

1)收件日期時間周圍的包裝,並在我的代碼中使用的這個代替日期時間。這將涉及到我的當前代碼庫的一些重寫。

2)每次運行測試/套件時動態生成數據集。

所以問題是:是否有可能重寫DateTimes的行爲總是提供一個特定的時間要求?

+0

你沒有接受答案。你能否澄清你在回答中尋找什麼,以及爲什麼給出的答案不能令你滿意。 – Gordon

+0

有完全相同的問題,從@shouze的答案PHP延長時間延長工作就像一個魅力。 –

回答

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類。另一種選擇是使用http://antecedent.github.io/patchwork

+0

謝謝Gordon - DateTime依賴項在我的大部分代碼中都是硬編碼的。我犯了一個使用它作爲原始的錯誤。所有其他的依賴注入都很容易模擬。我寧願不使用擴展模擬,因爲這會降低代碼的可移植性。雖然它可能是唯一的選擇!感謝您的回答。 –

2

添加到什麼@Gordon已經指出有一個,而hackish的,測試代碼的方式,依賴於當前時間:

我嘲笑了只有一個受保護的方法獲得你可以在需要創建一個類的問題上得到「全局」的價值,你可以請求類似當前時間的東西(這會更清晰,但是在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

您也可以使用它使用AOP PHP PECL extention要帶的東西類似紅寶石猴子打補丁的時間旅行者的lib https://github.com/rezzza/TimeTraveler

還有這個php擴展,靈感來自ruby timecop之一: https://github.com/hnw/php-timecop

+0

請在問題本身下面添加評論等解決方案.. – Lal

+0

我想...但我沒有權利這樣做ATM,新註冊;) – shouze

+0

TimeTraveler上次看到的時間被打破了。 timecop替代方式更好。 –

0

你可以改變你的實現來明確地用來實例化:

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

這不會改變你的類的行爲。但現在你可以mock time()提供了一個命名空間功能:

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

你也可以用我的包php-mock/php-mock-phpunit這樣做:當我使用Symfony's WebTestCase使用PHPUnit的測試執行功能測試

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框架(或者你正在使用的任何框架)不會使用你的庫,所以它需要處理的任何行爲,比如內部緩存/ cookie到期,不會受到這些變化的影響。

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 
    } 
}