我可以在PHPUnit中“模拟”时间吗?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我可以在PHPUnit中“模拟”时间吗?相关的知识,希望对你有一定的参考价值。
......不知道'mock'是否是正确的词。
无论如何,我有一个继承的代码库,我正在尝试编写一些基于时间的测试。试图不要太模糊,代码与查看项目的历史并确定该项目现在是否基于时间阈值有关。
在某些时候,我还需要测试在该历史记录中添加内容并检查阈值现在是否已更改(显然,更正)。
我正在测试的问题是我正在测试的部分代码是使用对time()的调用,所以我发现很难确切知道阈值时间应该是什么,基于我的事实我不确定何时会调用time()函数。
所以我的问题基本上是这样的:有没有办法让我“覆盖”time()调用,或以某种方式“模仿”时间,以便我的测试在“已知时间”工作?
或者我只是必须接受这样一个事实,即我将不得不在我正在测试的代码中执行某些操作,以某种方式允许我强制它在需要时使用特定时间?
无论哪种方式,是否有任何“常见做法”来开发对测试友好的时间敏感功能?
编辑:我的问题的一部分也是历史中发生的事情影响阈值的事实。这是我的部分问题的一个例子......
想象一下,你有一个香蕉,当你需要吃它时,你正试图解决这个问题。假设它将在3天内到期,除非它喷洒了一些化学物质,在这种情况下,我们会在施用喷雾时添加4天到期。然后,我们可以通过冻结它再添加3个月,但是如果它已经冷冻,那么我们只有1天的时间来解冻它。
所有这些规则都是由历史时间决定的。我同意我可以在几秒钟内使用Dominik的测试建议,但我的历史数据是什么?我应该在飞行中“创造”吗?
正如您可能或可能无法分辨的那样,我仍然试图了解所有这些“测试”概念;)
我最近提出了另一种解决方案,如果你使用的是php 5.3名称空间。您可以在当前命名空间内实现新的time()函数,并创建一个共享资源,您可以在其中设置测试中的返回值。然后对time()的任何不合格的调用都将使用你的新函数。
为了进一步阅读,我在我的blog中详细描述了它
这是fab的帖子的补充。我使用eval完成了基于命名空间的覆盖。这样,我可以运行它来进行测试,而不是我的其余代码。我运行类似于的函数:
function timeOverrides($namespaces = array()) {
$returnTime = time();
foreach ($namespaces as $namespace) {
eval("namespace $namespace; function time() { return $returnTime; }");
}
}
然后在测试设置中传入timeOverrides(array(...))
,这样我的测试只需要跟踪调用time()的名称空间。
免责声明:我写了这个库。
你可以使用Clock的ouzo-goodies模拟测试时间。
在代码中使用简单:
$time = Clock::now();
然后在测试中:
Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
就个人而言,我在测试的函数/方法中继续使用time()。在您的测试代码中,只需确保不测试与time()的相等性,而只是测试小于1或2的时间差(取决于函数执行所花费的时间)
对于那些使用symfony(> = 2.8)的人:Symfony的PHPUnit Bridge包含一个ClockMock功能,它覆盖了内置方法time
,microtime
,sleep
和usleep
。
见:http://symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking
我必须在应用程序本身(而不是单元测试中)模拟未来和过去日期中的特定请求。因此,对 DateTime :: now()的所有调用都应返回先前在整个应用程序中设置的日期。
我决定使用这个库https://github.com/rezzza/TimeTraveler,因为我可以在不改变所有代码的情况下模拟日期。
RezzzaTimeTraveler::enable();
RezzzaTimeTraveler::moveTo('2011-06-10 11:00:00');
var_dump(new DateTime()); // 2011-06-10 11:00:00
var_dump(new DateTime('+2 hours')); // 2011-06-10 13:00:00
在大多数情况下,这样做。它有一些优点:
- 你不必嘲笑任何东西
- 你不需要外部插件
- 你可以使用任何时间函数,不仅是time(),还有DateTime对象
- 您不需要使用名称空间。
它使用的是phpunit,但是您可以将它添加到任何其他测试框架中,您只需要像phpunit中的assertContains()一样工作。
1)将以下函数添加到测试类或引导程序中。默认的时间容差为2秒。您可以通过将第3个参数传递给assertTimeEquals或修改函数args来更改它。
private function assertTimeEquals($testedTime, $shouldBeTime, $timeTolerance = 2)
{
$toleranceRange = range($shouldBeTime, $shouldBeTime+$timeTolerance);
return $this->assertContains($testedTime, $toleranceRange);
}
2)测试示例:
public function testGetLastLogDateInSecondsAgo()
{
// given
$date = new DateTime();
$date->modify('-189 seconds');
// when
$this->setLastLogDate($date);
// then
$this->assertTimeEquals(189, $this->userData->getLastLogDateInSecondsAgo());
}
assertTimeEquals()将检查(189,190,191)的数组是否包含189。
如果执行测试功能需要不到2秒,则应该通过该测试以获得正确的工作功能。
它并不完美且超精确,但它非常简单,在很多情况下它足以测试你想要测试的东西。
最简单的解决方案是覆盖PHP time()函数并将其替换为您自己的版本。但是,您无法轻松替换内置的PHP函数(see here)。
除此之外,唯一的方法是抽象time()调用你自己的一些类/函数,这将返回你需要测试的时间。
或者,您可以在虚拟机中运行测试系统(操作系统)并更改整个虚拟计算机的时间。
您可以使用runkit扩展覆盖php time()函数。确保将runkit.internal override设置为On
使用[runkit] [1]扩展名:
define('MOCK_DATE', '2014-01-08');
define('MOCK_TIME', '17:30:00');
define('MOCK_DATETIME', MOCK_DATE.' '.MOCK_TIME);
private function mockDate()
{
runkit_function_rename('date', 'date_real');
runkit_function_add('date','$format="Y-m-d H:i:s", $timestamp=NULL', '$ts = $timestamp ? $timestamp : strtotime(MOCK_DATETIME); return date_real($format, $ts);');
}
private function unmockDate()
{
runkit_function_remove('date');
runkit_function_rename('date_real', 'date');
}
您甚至可以像这样测试模拟:
public function testMockDate()
{
$this->mockDate();
$this->assertEquals(MOCK_DATE, date('Y-m-d'));
$this->assertEquals(MOCK_TIME, date('H:i:s'));
$this->assertEquals(MOCK_DATETIME, date());
$this->unmockDate();
}
以上是关于我可以在PHPUnit中“模拟”时间吗?的主要内容,如果未能解决你的问题,请参考以下文章
您可以在设置中排除一种测试方法吗? (Laravel 测试 Phpunit)