Могу ли я "издеваться" над временем в PHPUnit?


... не зная, подходит ли слово "издеваться".

В любом случае, у меня есть унаследованная база кода, для которой я пытаюсь написать некоторые тесты, основанные на времени. Стараясь не быть слишком расплывчатым, код связан с изучением истории элемента и определением, установил ли этот элемент пороговое значение времени.

В какой-то момент мне также нужно проверить, добавив что-то в эту историю, и проверить, что порог теперь изменен (и, очевидно, правильный).

В проблема, с которой я сталкиваюсь, заключается в том, что часть кода, который я тестирую, использует вызовы time(), и поэтому мне очень трудно точно определить, каким должно быть пороговое время, основываясь на том факте, что я не совсем уверен, когда именно будет вызвана функция time().

Итак, мой вопрос в основном таков: есть ли у меня какой-то способ "переопределить" вызов time() или каким-то образом "имитировать" время, чтобы мои тесты работали в "известное время"?

Или мне просто нужно принять тот факт, что мне придется что-то сделать в коде, который я тестирую, чтобы каким-то образом позволить мне заставить его использовать определенное время, если потребуется?

В любом случае, существуют ли какие-либо "общие методы" для разработки функциональных возможностей, зависящих от времени, которые удобны для тестирования?

Редактировать: Частью моей проблемы также является тот факт, что время, в течение которого происходили события в истории, влияет на порог. Вот пример части моей проблемы...

Представьте, что у вас есть банан, и вы пытаетесь потренироваться когда его нужно съесть. Допустим, срок его действия истечет в течение 3 дней, если только он не был распылен каким-либо химическим веществом, и в этом случае мы добавим 4 дня к истечению срока действия с момента нанесения спрея . Затем мы можем добавить к нему еще 3 месяца, заморозив его, но если он был заморожен, то у нас есть только 1 день, чтобы использовать его после того, как он оттает.

Все эти правила продиктованы историческим временем. Я согласен, что мог бы воспользоваться предложением Доминика о тестировании в течение нескольких секунд, но что из мои исторические данные? Должен ли я просто "создать" это на лету?

Как вы можете или не можете сказать, я все еще пытаюсь разобраться во всей этой концепции "тестирования";)

 61
Author: Narcissus, 2010-03-03

10 answers

Недавно я придумал другое решение, которое отлично подходит, если вы используете пространства имен PHP 5.3. Вы можете реализовать новую функцию time() в текущем пространстве имен и создать общий ресурс, в котором вы зададите возвращаемое значение в своих тестах. Тогда любой неквалифицированный вызов time() будет использовать вашу новую функцию.

Для дальнейшего чтения я подробно описал это в своем блоге

 53
Author: Fabian Schmengler, 2016-04-14 19:18:59

Отказ от ответственности: Я написал эту библиотеку.

Вы можете имитировать время для теста, используя Часы из узо-лакомства.

В коде используйте просто:

$time = Clock::now();

Затем в тестах:

Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
 5
Author: Piotr Olaszewski, 2017-07-10 08:51:39

Лично я продолжаю использовать time() в тестируемых функциях/методах. В своем тестовом коде просто убедитесь, что вы проверяете не на равенство со временем(), а просто на разницу во времени менее 1 или 2 (в зависимости от того, сколько времени требуется для выполнения функции)

 3
Author: Dominik, 2010-03-03 14:11:42

Мне пришлось имитировать конкретный запрос в будущем и в прошлую дату в самом приложении (не в модульных тестах). Следовательно, все вызовы \DateTime::now() должны возвращать дату, которая была ранее установлена во всем приложении.

Я решил пойти с этой библиотекой 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
 2
Author: SenG, 2015-09-06 01:21:15

В большинстве случаев этого будет достаточно. У него есть некоторые преимущества:

  • тебе не нужно ни над чем издеваться
  • вам не нужны внешние плагины
  • вы можете использовать любую функцию времени, не только time(), но и объекты DateTime
  • вам не нужно использовать пространства имен.

Он использует phpunit, но вы можете добавить его в любую другую платформу тестирования, вам просто нужна функция, которая работает как assertContains() из phpunit.

1) Добавьте функцию ниже в свой тестовый класс или бутстрэп. Допуск по умолчанию для времени составляет 2 секунды. Вы можете изменить его, передав 3-й аргумент в assertTimeEquals или изменив аргументы функции.

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 секунд.

Это не идеально и не сверхточно, но это очень просто, и во многих случаях этого достаточно для тестирования что вы хотите проверить.

 2
Author: Konrad Gałęzowski, 2015-09-29 08:37:10

Для тех из вас, кто работает с symfony (>=2.8): Мост PHPUnit Symfony включает функцию синхронизации, которая переопределяет встроенные методы time, microtime, sleep и usleep.

См.: http://symfony.com/doc/2.8/components/phpunit_bridge.html#clock-mocking

 2
Author: simon.ro, 2017-12-17 16:34:54

Самым простым решением было бы переопределить функцию PHP time() и заменить ее своей собственной версией. Однако вы не можете легко заменить встроенные функции PHP ( см. Здесь ).

Короче говоря, единственный способ - это абстрагировать вызов time() к какому-либо собственному классу/функции, который вернет время, необходимое для тестирования.

В качестве альтернативы вы можете запустить тестовую систему (операционную систему) на виртуальной машине и изменить время всего виртуального компьютера.

 1
Author: Milan Babuškov, 2010-03-03 14:56:09

Вы можете переопределить функцию php time(), используя расширение runkit. Убедитесь, что вы установили runkit.internal_overide в значение On

 1
Author: Vlad Balmos, 2012-04-23 13:56:52

Использование расширения [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();
}
 1
Author: jhvaras, 2014-01-10 10:56:06

Вот дополнение к сообщению фаба. Я сделал переопределение на основе пространства имен, используя оценку. Таким образом, я могу просто запустить его для тестов, а не для остальной части моего кода. Я запускаю функцию, аналогичную:

function timeOverrides($namespaces = array()) {
  $returnTime = time();
  foreach ($namespaces as $namespace) {
    eval("namespace $namespace; function time() { return $returnTime; }");
  }
}

Затем передайте timeOverrides(array(...)) в настройках теста, чтобы мои тесты должны были отслеживать только то, в каких пространствах имен вызывается time().

 1
Author: Photis, 2014-03-07 04:26:05