php 我可以在 PHPUnit 中“模拟”时间吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2371854/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I "Mock" time in PHPUnit?
提问by Narcissus
... not knowing if 'mock' is the right word.
...不知道“模拟”是否正确。
Anyway, I have an inherited code-base that I'm trying to write some tests for that are time-based. Trying not to be toovague, the code is related to looking at the history of an item and determining if that item has now based a time threshold.
无论如何,我有一个继承的代码库,我正在尝试为其编写一些基于时间的测试。尽量不要太含糊,代码与查看项目的历史记录并确定该项目现在是否基于时间阈值有关。
At some point I also need to test adding something to that history and checking that the threshold is now changed (and, obviously, correct).
在某些时候,我还需要测试向该历史记录添加一些内容并检查阈值现在是否已更改(并且显然是正确的)。
The problem that I'm hitting is that part of the code I'm testing is using calls to time() and so I'm finding it really hard to know exactly what the threshold time should be, based on the fact that I'm not quite sure exactly when that time() function is going to be called.
我遇到的问题是我正在测试的代码的一部分是使用对 time() 的调用,所以我发现很难确切地知道阈值时间应该是多少,基于我'我不太确定何时调用 time() 函数。
So my question is basically this: is there some way for me to 'override' the time() call, or somehow 'mock out' the time, such that my tests are working in a 'known time'?
所以我的问题基本上是这样的:有什么方法可以让我“覆盖” time() 调用,或者以某种方式“模拟”时间,以便我的测试在“已知时间”工作?
Or do I just have to accept the fact that I'm going to have to do something in the code that I'm testing, to somehow allow me to force it to use a particular time if need be?
或者我是否必须接受这样一个事实,即我将不得不在我正在测试的代码中做一些事情,以某种方式允许我在需要时强制它使用特定时间?
Either way, are there any 'common practices' for developing time-sensitive functionality that is test friendly?
无论哪种方式,是否有任何“通用做法”来开发对测试友好的时间敏感功能?
Edit: Part of my problem, too, is the fact that the time that things occurred in history affect the threshold. Here's an example of part of my problem...
编辑:我的部分问题也是这样一个事实,即历史上发生的事情会影响阈值。这是我的部分问题的示例...
Imagine you have a banana and you're trying to work out when it needs to be eaten by. Let's say that it will expire within 3 days, unless it was sprayed with some chemical, in which case we add 4 days to the expiry, from the time the spray was applied. Then, we can add another 3 months to it by freezing it, but if it's been frozen then we only have 1 day to use it after it thaws.
想象一下,你有一个香蕉,你正在努力解决什么时候需要吃掉它。假设它会在 3 天内过期,除非它喷洒了某种化学物质,在这种情况下,我们将自喷洒之日起将过期时间增加 4 天。然后,我们可以通过冷冻再增加 3 个月,但如果它已经冷冻,那么在它解冻后我们只有 1 天的时间使用它。
All of these rules are dictated by historical timings. I agree that I could use the Dominik's suggestion of testing within a few seconds, but what of my historical data? Should I just 'create' that on the fly?
所有这些规则都是由历史时间决定的。我同意我可以在几秒钟内使用 Dominik 的建议进行测试,但是我的历史数据呢?我应该即时“创建”它吗?
As you may or may not be able to tell, I'm still trying to get a hang of all of this 'testing' concept ;)
正如您可能知道也可能不知道,我仍在尝试掌握所有这些“测试”概念;)
采纳答案by Fabian Schmengler
I recently came up with another solution that is great if you are using PHP 5.3 namespaces. You can implement a new time() function inside your current namespace and create a shared resource where you set the return value in your tests. Then any unqualified call to time() will use your new function.
我最近想出了另一个解决方案,如果您使用的是 PHP 5.3 命名空间,它会很棒。您可以在当前命名空间内实现一个新的 time() 函数并创建一个共享资源,您可以在其中设置测试中的返回值。然后任何对 time() 的不合格调用都将使用您的新函数。
For further reading I described it in detail in my blog
为了进一步阅读,我在我的博客中详细描述了它
回答by Piotr Olaszewski
Disclaimer: I wrote this library.
免责声明:我编写了这个库。
You can mock time for test using Clockfrom ouzo-goodies.
你可以嘲笑时间使用测试时钟从茴香烈酒,好吃的东西。
In code use simply:
在代码中简单地使用:
$time = Clock::now();
Then in tests:
然后在测试中:
Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
回答by SenG
I had to simulate a particular request in future and past date in the app itself (not in Unit Tests). Hence all calls to \DateTime::now() should return the date that was previously set throughout the app.
我必须在应用程序本身(而不是在单元测试中)模拟未来和过去日期的特定请求。因此,对 \DateTime::now() 的所有调用都应返回之前在整个应用程序中设置的日期。
I decided to go with this library https://github.com/rezzza/TimeTraveler, since I can mock the dates without altering all the codes.
我决定使用这个库https://github.com/rezzza/TimeTraveler,因为我可以在不更改所有代码的情况下模拟日期。
\Rezzza\TimeTraveler::enable();
\Rezzza\TimeTraveler::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
回答by simon.ro
For those of you working with symfony (>= 2.8): Symfony's PHPUnit Bridge includes a ClockMock feature that overrides the built-in methods time, microtime, sleepand usleep.
对于那些你用symfony(> = 2.8)工作:Symfony的的PHPUnit的桥包括一个ClockMock功能,覆盖内建的方法time,microtime,sleep和usleep。
See: http://symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking
请参阅:http: //symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking
回答by Dominik
Personally, I keep using time() in the tested functions/methods. In your test code, just make sure to not test for equality with time(), but simply for a time difference of less than 1 or 2 (depending on how much time the function takes to execute)
就个人而言,我一直在测试的函数/方法中使用 time()。在您的测试代码中,只需确保不测试与 time() 是否相等,而只是测试小于 1 或 2 的时间差(取决于函数执行所需的时间)
回答by Konrad Ga??zowski
In most cases this will do. It has some advantages:
在大多数情况下,这可以做到。它有一些优点:
- you don't have to mock anything
- you don't need external plugins
- you can use any time function, not only time() but DateTime objects as well
- you don't need to use namespaces.
- 你不必嘲笑任何东西
- 你不需要外部插件
- 您可以使用任何时间函数,不仅是 time() 还可以使用 DateTime 对象
- 您不需要使用命名空间。
It's using phpunit, but you can addapt it to any other testing framework, you just need function that works like assertContains() from phpunit.
它使用 phpunit,但您可以将其添加到任何其他测试框架中,您只需要像 phpunit 中的 assertContains() 一样工作的函数。
1) Add below function to your test class or bootstrap. Default tolerance for time is 2 secs. You can change it by passing 3rd argument to assertTimeEquals or modify function args.
1)将以下函数添加到您的测试类或引导程序。时间的默认容差为 2 秒。您可以通过将第三个参数传递给 assertTimeEquals 或修改函数 args 来更改它。
private function assertTimeEquals($testedTime, $shouldBeTime, $timeTolerance = 2)
{
$toleranceRange = range($shouldBeTime, $shouldBeTime+$timeTolerance);
return $this->assertContains($testedTime, $toleranceRange);
}
2) Testing example:
2) 测试示例:
public function testGetLastLogDateInSecondsAgo()
{
// given
$date = new DateTime();
$date->modify('-189 seconds');
// when
$this->setLastLogDate($date);
// then
$this->assertTimeEquals(189, $this->userData->getLastLogDateInSecondsAgo());
}
assertTimeEquals() will check if array of (189, 190, 191) contains 189.
assertTimeEquals() 将检查 (189, 190, 191) 的数组是否包含 189。
This test should be passed for correct working function IF executing test function takes less then 2 seconds.
如果执行测试功能的时间少于 2 秒,则应通过此测试以确保正确的工作功能。
It's not perfect and super-accurate, but it's very simple and in many cases it's enough to test what you want to test.
它并不完美和超级准确,但它非常简单,在许多情况下足以测试您想要测试的内容。
回答by Vlad Balmos
You can overide php's time() function using the runkit extension. Make sure you set runkit.internal_overide to On
您可以使用 runkit 扩展覆盖 php 的 time() 函数。确保将 runkit.internal_overide 设置为 On
回答by jhvaras
Using [runkit][1] extension:
使用 [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');
}
You can even test the mock like this:
你甚至可以像这样测试模拟:
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();
}
回答by Photis
Here's an addition to fab's post. I did the namespace based override using an eval. This way, I can just run it for tests and not the rest of my code. I run a function similar to:
这是 fab 帖子的补充。我使用 eval 进行了基于命名空间的覆盖。这样,我可以只运行它进行测试,而不是我的其余代码。我运行一个类似于:
function timeOverrides($namespaces = array()) {
$returnTime = time();
foreach ($namespaces as $namespace) {
eval("namespace $namespace; function time() { return $returnTime; }");
}
}
then pass in timeOverrides(array(...))in the test setup so that my tests only have to keep track of what namespaces time() is called in.
然后传入timeOverrides(array(...))测试设置,以便我的测试只需要跟踪调用 time() 的命名空间。
回答by Milan Babu?kov
Simplest solution would be to override PHP time() function and replace it with your own version. However, you cannot replace built-in PHP functions easily (see here).
最简单的解决方案是覆盖 PHP time() 函数并将其替换为您自己的版本。但是,您无法轻松替换内置 PHP 函数(请参阅此处)。
Short of that, the only way is to abstract time() call to some class/function of your own that would return the time you need for testing.
除此之外,唯一的方法是将 time() 调用抽象为您自己的某个类/函数,以返回您测试所需的时间。
Alternatively, you could run the test system (operating system) in a virtual machine and change the time of the entire virtual computer.
或者,您可以在虚拟机中运行测试系统(操作系统)并更改整个虚拟机的时间。

