2010-03-03 4 views
60

... не зная, является ли «макет» правильным словом.Могу ли я «Mock» время в PHPUnit?

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

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

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

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

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

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

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

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

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

Как вы можете или не можете быть в состоянии сказать, что я все еще пытаюсь получить повесить все это понятия «тестирования»;)

ответ

51

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

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

+1

Мне очень нравится эта идея Фабиан. Дополнительным преимуществом является то, что он заставляет моих соработников обновляться до 5.3;) – Narcissus

+0

Это очень * полезно, спасибо за то, что поделились этой техникой с нами, Фабиан - высоко ценим! – MicE

+0

гений произведение здесь. Пространства имен для функций делают полезным заменить встроенные функции для тестирования. – mauris

3

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

+0

На данный момент это выглядит именно так, я должен идти. Я добавил пример для моей проблемы, если это поможет. Благодарю. – Narcissus

+0

Я видел ваш пример. Опять же, лично, для такого рода тестов я использую метод установки phpunit для подготовки «правильных» исторических данных (например, внутри базы данных). – Dominik

+0

Это сделает ваши тесты очень хрупкими. Они могут провалиться, по-видимому, без причины, всякий раз, когда испытуемый процесс испытывает задержку (по какой-либо причине). –

1

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

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

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

+0

Спасибо, Милан: хотя я согласен, что запуск в виртуальной машине будет возможностью «заставить» время, я думаю, мне все равно придется учитывать «переменные времени исполнения», и поэтому я все равно делаю то, что предложил Доминик. Интересная идея, однако, спасибо. – Narcissus

1

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

1

Используя [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

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

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

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

5

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

Вы можете высмеять время для испытания с использованием Clock от ouzo-goodies.

При использовании коды просто:

$time = Clock::now(); 

Тогда в тестах:

Clock::freeze('2014-01-07 12:34'); 
$result = Class::getCurrDate(); 
$this->assertEquals('2014-01-07', $result); 
+21

Когда вы ссылаетесь на свое собственное программное обеспечение, вы должны включить отказ от ответственности, позволяя всем знать, что вы его написали. – Will

2

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

кажется классным для тех, кто использует 'new \ DateTime()' в своем коде. Но как это должно быть установлено? Нет информации в репозитории github. – kekko12

2

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

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

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

1) Добавьте функцию ниже к вашему тестовому классу или начальной загрузке. Допуск по умолчанию для времени составляет 2 секунды. Вы можете изменить его, передав третий аргумент 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 секунд.

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

Смежные вопросы