如何测试使用 DateTime 获取当前时间的函数?
How can I test a function that uses DateTime to get the current time?
我在 Whosebug 上看到的大多数答案都没有使用 DateTime
对象,而是使用 date()
函数。这使它们成为非常肮脏的解决方案(覆盖 date()
、模拟被测对象的受保护功能等)。
有没有办法模拟DateTime
,有效地模拟当前的date/time?
例如,这是我要测试的代码:
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 中设置日期和时间的地方:
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;
}
如您所见,我创建了一个相当冗长的闭包,只是为了排除包含当前时间的字段的比较,我觉得这会损害我的测试的可读性。
此外,为了让使用此 class 的人易于使用,我不想让他们将当前时间传递给 create()
函数。我相信向我的 classes 添加奇怪的行为只是为了让它们可测试意味着我做错了什么。
因此解决此问题的标准方法依赖于接受在当前实现中您对提供当前时间的对象具有静态、隐式、未声明的依赖性(包装在 DateTime 对象的新实例中)。如果您使用自己的代码(而不是 framework/language 中的 class)执行此操作,您也将无法轻松测试。
解决方案是停止使用隐式未声明的依赖项并显式声明隐式依赖项。我会通过创建一个 DateTimeProvider
(或 DateTimeFactory
)接口来做到这一点,它有一个方法 GetCurrentDateTime
。将其传递给 CommandHistoryCreator
的构造函数,并将其传递给 CommandHistory
构造函数。 CommandHistory
然后将要求提供者获取当前日期时间对象而不是自己创建一个新对象,并且可以按原样继续。
这将允许您在测试中提供模拟 DateTime
并检查 CommandHistory
是否使用正确的 DateTime
持久化
我在 Whosebug 上看到的大多数答案都没有使用 DateTime
对象,而是使用 date()
函数。这使它们成为非常肮脏的解决方案(覆盖 date()
、模拟被测对象的受保护功能等)。
有没有办法模拟DateTime
,有效地模拟当前的date/time?
例如,这是我要测试的代码:
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 中设置日期和时间的地方:
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;
}
如您所见,我创建了一个相当冗长的闭包,只是为了排除包含当前时间的字段的比较,我觉得这会损害我的测试的可读性。
此外,为了让使用此 class 的人易于使用,我不想让他们将当前时间传递给 create()
函数。我相信向我的 classes 添加奇怪的行为只是为了让它们可测试意味着我做错了什么。
因此解决此问题的标准方法依赖于接受在当前实现中您对提供当前时间的对象具有静态、隐式、未声明的依赖性(包装在 DateTime 对象的新实例中)。如果您使用自己的代码(而不是 framework/language 中的 class)执行此操作,您也将无法轻松测试。
解决方案是停止使用隐式未声明的依赖项并显式声明隐式依赖项。我会通过创建一个 DateTimeProvider
(或 DateTimeFactory
)接口来做到这一点,它有一个方法 GetCurrentDateTime
。将其传递给 CommandHistoryCreator
的构造函数,并将其传递给 CommandHistory
构造函数。 CommandHistory
然后将要求提供者获取当前日期时间对象而不是自己创建一个新对象,并且可以按原样继续。
这将允许您在测试中提供模拟 DateTime
并检查 CommandHistory
是否使用正确的 DateTime