Unit testing 如何测试使用DateTime获取当前时间的函数?

Unit testing 如何测试使用DateTime获取当前时间的函数?,unit-testing,symfony,datetime,phpunit,mockery,Unit Testing,Symfony,Datetime,Phpunit,Mockery,我在StackOverflow上看到的大多数答案都没有使用DateTime对象,而是使用date()函数。这使它们成为非常肮脏的解决方案(覆盖date(),模仿受测对象的受保护函数,等等) 有没有办法模拟日期时间,有效模拟当前日期/时间 例如,下面是我要测试的代码: public function __construct(UserInterface $user, EntityManager $manager) { $this->user = $user; $this->

我在StackOverflow上看到的大多数答案都没有使用
DateTime
对象,而是使用
date()
函数。这使它们成为非常肮脏的解决方案(覆盖
date()
,模仿受测对象的受保护函数,等等)

有没有办法模拟
日期时间
,有效模拟当前日期/时间

例如,下面是我要测试的代码:

public function __construct(UserInterface $user, EntityManager $manager)
{
    $this->user = $user;
    $this->manager = $manager;
}

public function create(Tunnel $tunnel, $chain, $response)
{
    $history = new CommandHistory();

    $history->setTunnel($tunnel)
        ->setCommand($chain)
        ->setResponse($response)
        ->setUser($this->user)
    ;

    $this->manager->persist($history);
    $this->manager->flush();
}
这里是我在
CommandHistory
类中设置日期和时间的地方:

class CommandHistory
{
    // Property definitions...

    public function __construct()
    {
        $this->time = new \DateTime();
    }
}
下面是我的单元测试:

public function testCreate()
{
    $user = new User();
    $manager = $this->mockManagerWithUser($user);

    $tunnel = $this->tunnel;
    $chain = 'Commands`Chain';
    $response = 'This is the response!';

    $creator = new CommandHistoryCreator($user, $manager);
    $creator->create($tunnel, $chain, $response);
}

protected function mockManagerWithUser(UserInterface $user)
{
    $manager = \Mockery::mock('Doctrine\ORM\EntityManager');

    $manager->shouldReceive('persist')->once()->with(\Mockery::on(function(CommandHistory $argument) use ($user) {
        return
            $argument->getCommand() === 'Commands`Chain'
            && $argument->getResponse() === 'This is the response!'
            && $argument->getTunnel() === $this->tunnel
            && $argument->getUser() === $user
        ;
    }));
    $manager->shouldReceive('flush')->once()->withNoArgs();

    return $manager;
}
如您所见,我创建了一个相当冗长的闭包,只是为了排除包含当前时间的字段的比较,我觉得这会损害测试的可读性


另外,为了使使用该类的人保持易用性,我不想让他们在当前时间内传递到
create()
函数。我相信向我的类中添加奇怪的行为只是为了使它们可测试,这意味着我做错了什么。

因此解决这个问题的标准方法依赖于接受这样一个事实,即在当前的实现中,您对提供当前时间的对象有一个静态的、隐式的、未声明的依赖关系(包装在DateTime对象的新实例中)。如果您使用自己的代码(而不是框架/语言中的类)执行此操作,您也无法轻松进行测试

解决方案是停止使用隐式未声明的依赖项,并明确声明隐式依赖项。我可以通过创建
DateTimeProvider
(或
DateTimeFactory
)来实现这一点接口,该接口具有一个方法
GetCurrentDateTime
。将该方法传递到您的
CommandHistoryCreator
的构造函数中,并将其传递到
CommandHistory
构造函数中。然后
CommandHistory
将要求提供程序获取当前日期时间对象,而不是自己创建一个新的对象,并且可以携带照现在的样子


这将允许您在测试中提供模拟的
DateTime
,并检查
CommandHistory
是否使用正确的
DateTime

保存当前时间?我对PHP不太熟悉,但我看不到上面的任何代码中使用的日期或时间…我假设这是一个属性CommandHistory对象,您没有显示它…是的,在
CommandHistory
的构造函数中。我刚才也把它添加到了我的问题中。谢谢提醒:-)好吧,这种方法的问题是,显式地显示这种依赖关系并不能解决其他开发人员的任何问题。他们现在必须做一些在PHP领域非常不熟悉的事情,即将
DateTimeFactory
传递到PHP实体的构造函数中。告诉那些开发人员测试他们的代码,他们也需要这个。。。也许这是因为缺乏良好的代码测试而不熟悉的。@parhamdoustdar这就是为什么我开始回答“依赖于接受在您当前的实现中您有一个静态的、隐式的、未声明的依赖关系”。在大多数语言中,datetime是这样的,而在大多数语言中,人们并不习惯于“仅仅为了获取当前时间”而提供工厂。如果你让构造器需要这个接口,人们就会看到他们需要它,并且习惯于使用它。