Unit testing PHPUnit:如何嘲笑今天';如果不把它作为一个参数传递,它的日期是多少?

Unit testing PHPUnit:如何嘲笑今天';如果不把它作为一个参数传递,它的日期是多少?,unit-testing,phpunit,Unit Testing,Phpunit,我正在我的类上测试一个方法,该方法执行一些日期检查。问题是该方法依赖于今天的日期(每天都在变化),这使得测试变得困难。我怎样才能模拟今天的日期,使我的测试明天仍能通过?我对PHP一无所知,但在Java和C中,我都会传入一个描述为“时钟”的东西——不是今天的日期本身,而是一个可以询问当前日期/时间的对象。然后在单元测试中,您可以传入一个对象,该对象可以给出您想要的任何日期,包括硬编码到测试中的日期 这在PHP中也适用吗?虽然Jon的答案是“正确的方法”,但另一个选择是使用临时替换date()和/或

我正在我的类上测试一个方法,该方法执行一些日期检查。问题是该方法依赖于今天的日期(每天都在变化),这使得测试变得困难。我怎样才能模拟今天的日期,使我的测试明天仍能通过?

我对PHP一无所知,但在Java和C中,我都会传入一个描述为“时钟”的东西——不是今天的日期本身,而是一个可以询问当前日期/时间的对象。然后在单元测试中,您可以传入一个对象,该对象可以给出您想要的任何日期,包括硬编码到测试中的日期

这在PHP中也适用吗?

虽然Jon的答案是“正确的方法”,但另一个选择是使用临时替换
date()
和/或
time()
函数,这些函数为测试返回固定值

  • 确保在
    php.ini
    中设置
    runkit.internal\u override
    ,以便可以重命名内置函数
  • 使用重命名原始函数
  • 使用原始名称重命名模拟函数
  • 测试
  • 重命名你的模拟背部
  • 重新命名原始文件
  • 下面是一些完全未经测试的代码来帮助您实现这一点:

    function mock_function($original, $mock) {
        runkit_function_rename($original, $original . '_original');
        runkit_function_rename($mock, $original);
    }
    
    function unmock_function($original, $mock) {
        runkit_function_rename($original, $mock);
        runkit_function_rename($original . '_original', $original);
    }
    

    您应该在
    setUp()
    tearDown()
    方法中使用这些方法,以确保不会干扰后续的其他测试。

    我知道您不想将其作为参数传入。但也许你可以重新考虑一下

    当从外部作为参数传递时,日期不是无关紧要的技术细节,而是重要的功能规则。你不需要以下任何一项吗

    • 尽管当前日期可以是常规用例,但该规则可能适用于其他日期。这样,您的代码将更加通用,并且可以在以后的用例中工作,无需修改。这经常发生在我身上
    • 一些代码可以在算法中使用当前日期。因为计算机的速度不是无限的,几个会得到不同的瞬间。。。这在功能上合乎逻辑吗?或者使用相同的瞬间(例如,用户按下“点火”按钮的瞬间)会更准确吗?如果这些时间在您的数据库中都不同,即使它们对您的用户来说代表同一个瞬间,思考您以后如何在数据库中请求这些时间
    如果您不想通过日期来保留外部接口,那么最好使用“seam”来提供日期:

    class MyClass {
      public function toBeTested() {
        $theDate = $this->getDate();
        ...
      }
    
      protected function getDate() {
        return date();
      }
    }
    
    在一般情况下,这个类只能正常工作。 然后,在单元测试中,不是测试MyClass,而是使用覆盖getDate()函数的内部类扩展MyClass:

    use PHPUnit\Framework\TestCase;
    
    class MyTest extends TestCase {
    
      static $testDate;
    
      public function testToBeTested() {
        //set the date to be used
        MyTest::testDate = '1/2/2000';
        $classUnderTest = new MyClassWithDate();
        $this->assertEquals('expected', $classUnderTest->toBeTested());
    
      }
    }
    
    //just pass back the expected date
    class MyClassWithDate extends MyClass {
      protected function getDate() {
        return MyTest::testDate;
      }
    }
    
    在这段代码中,您针对真实类的扩展进行测试,但是您的扩展重写了seam函数(getDate()),并返回您希望用于此特定测试的日期


    再说一次,如果有一些严重的语法错误,很抱歉,这是徒手写的。

    我不能像David说的那样重命名两次,所以我得到了如下结果:

    function mockDate()
    {
        runkit_function_rename('date', 'test_date_override');
        runkit_function_add('date','$format=NULL,$timestamp=NULL,$locale=NULL', 'return DATEMOCK;');
    }
    
    function unmockDate()
    {
        runkit_function_remove('date');
        runkit_function_rename('test_date_override', 'date');
    }
    

    问题是,该方法取决于今天的日期(每天都在变化),+1让我咯咯地笑了起来。可能是重复的是,不幸的是,这意味着将一个轻量级函数
    date()
    替换为一个必须在任何地方使用的对象。这是正确的方法,但是如果没有像Spring框架这样的好容器,那么会有很多工作要做。