Могу ли я "макетировать" время в PHPUnit? - PullRequest
68 голосов
/ 03 марта 2010

... не зная, правильное ли слово «mock».

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

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

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

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

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

В любом случае, есть ли «общие практики» для разработки чувствительной ко времени функциональности, удобной для тестирования?

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

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

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

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

Ответы [ 10 ]

58 голосов
/ 17 марта 2011

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

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

6 голосов
/ 07 января 2015

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

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

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

$time = Clock::now();

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

Clock::freeze('2014-01-07 12:34');
$result = Class::getCurrDate();
$this->assertEquals('2014-01-07', $result);
4 голосов
/ 17 декабря 2017

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

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

3 голосов
/ 06 сентября 2015

Мне пришлось смоделировать конкретный запрос в будущем и в прошлом в самом приложении (не в модульных тестах). Следовательно, все вызовы \ 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
3 голосов
/ 03 марта 2010

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

2 голосов
/ 29 сентября 2015

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

  • тебе не нужно ничего издеваться
  • вам не нужны внешние плагины
  • Вы можете использовать любую функцию времени, не только объекты 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 голосов
/ 27 декабря 2013

Использование расширения [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();
}
2 голосов
/ 23 апреля 2012

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

1 голос
/ 07 марта 2014

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

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

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

1 голос
/ 03 марта 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...