2009-09-11 2 views
2

У меня есть веб-приложение, в котором я хочу запустить некоторые системные тесты, и для этого мне нужно будет переместить системное время. Приложение использует DateTime на всем протяжении.Как я могу тестировать приложения Perl с использованием системного времени?

У кого-нибудь есть рекомендации относительно того, как изменить время, которое DateTime-> теперь сообщает? Единственное, что приходит на ум, - это подклассификация DateTime и возиться со всеми линиями «использования», но это кажется довольно инвазивным.

Обратите внимание на ответы:

Все три будут работать нормально, но Hook :: LexWrap один является один я выбрал, потому что (а) Я хочу, чтобы переместить часы, а не трясти его немного (который это больше цель того, что делает Time :: Mock и друзья); (b) Я постоянно использую DateTime, и я рад, что у меня возникли ошибки, если я случайно не использовал; и (c) Крюк :: LexWrap просто более изящный, чем взломать в таблице символов, для всего, что он делает то же самое. (Кроме того, он оказывается зависимым от некоторого модуля, который я уже установил, поэтому мне даже не пришлось использовать CPAN ...)

ответ

1

Вы можете использовать ввод кода через Hook::LexWrap, чтобы перехватить метод now().

use Hook::LexWrap; 

use DateTime; 

# Use real now 
test(); 

{ 
    my $wrapper = wrap 'DateTime::now', 
     post => sub { 
      $_[-1] = DateTime->from_epoch(epoch => 0); 
     }; 

    # Use fake now 
    test(); 

} 

# use real now again 
test(); 

sub test { 
    my $now = DateTime->now; 

    print "The time is $now\n"; 
} 
+0

http: //search.cpan.org/perldoc/Hook :: LexWrap также будет работать как ссылка. –

+1

Интересно, и указатель документа был очень полезен. Я бы порекомендовал также упоминать версию, не связанную с областью, в ответе, так как это то, что я изначально искал, но обе версии будут полезны для меня. – ijw

4

Я думаю, что крюк :: LexWrap является излишним для этой ситуации. Легче просто переопределить такую ​​простую функцию.

use DateTime; 

my $offset; 

BEGIN { 
    $offset = 24 * 60 * 60; # Pretend it's tomorrow 

    no warnings 'redefine'; 

    sub DateTime::now 
    { 
    shift->from_epoch(epoch => ($offset + scalar time), @_) 
    } 
} # end BEGIN 

Вы можете заменить my $offset с our $offset, если вам нужно получить доступ к $offset извне файла, который содержит этот код.

Вы можете настроить $offset в любое время, если хотите изменить представление DateTime текущего времени во время прогона.

Расчет $offset должен, вероятно, быть более сложным, чем показано выше. Например, чтобы установить «текущее время» к абсолютному времени:

my $want = DateTime->new(
    year => 2009, 
    month => 9, 
    day => 14, 
    hour => 12, 
    minute => 0, 
    second => 0, 
    time_zone => 'America/Chicago', 
); 

my $current = DateTime->from_epoch(epoch => scalar time); 

$offset = $want->subtract_datetime_absolute($current)->in_units('seconds'); 

Но вы, вероятно, хотите, чтобы вычислить определенное количество секунд, чтобы добавить к текущему времени, так что время будет продвигаться нормально после этого. Проблема с использованием add(days => 1); в переопределенном методе now заключается в том, что такие вещи, как изменения DST, заставят время прыгать в неправильное псевдо-время.

+0

То, что вы показываете, отлично работает, если вам нужно всего лишь отменить теперь() до одного поведения в течение всего прогона. Если ОП меняет поведение now() несколько раз и на узкие части тестового набора, я считаю, что H :: LW - лучший выбор. Кроме того, чтобы притвориться, что завтра, не лучше ли использовать возможности DateTime и сделать это правильно? DateTime-> now-> add (days => 1); – daotoad

+1

Ну, вы можете легко изменить '$ offset' в любое время во время прогона. Что касается использования 'add' вместо этого, это зависит от того, что вы пытаетесь сделать. Я просто приводил один пример возможного нового определения. – cjm

+0

Измените «my $ offset = ...» на «наш $ _offset = ...», а затем тестовый скрипт может twiddle $ DateTime :: _ offset столько, сколько ему нравится. – Ether

11

Вместо того, подход на высоком уровне и упаковка DateTime конкретно, вы можете захотеть взглянуть на модули Test::MockTime и Time::Mock, которые замещают функции низкого уровня, DateTime и т.д. делают использование, и (если повезет) будет делать правильную вещь на любой с учетом времени. Для меня это кажется более надежным способом тестирования.

+0

Это похоже на лучшее решение. Тесты будут продолжать работать, когда проверенный код переключится на другую библиотеку для обработки даты и времени. – daotoad

+0

Действительно, хотя в этом случае я хочу стандартизировать DateTime во всем приложении. Так что испытание, которое терпит неудачу, когда не использовать DateTime - это хорошо. – ijw

1

При проектировании нового класса с возможностью проверки в идеале идеальным решением является возможность ввода новых объектов даты.

Однако для существующего кода с использованием DateTime->now и DateTime->today возможно, подходящее решение, ниже. Я включаю его здесь как способ сделать это, не введя Hook :: LexWrap как зависимость и не влияя на поведение в глобальном масштабе.

{ 
    no strict 'refs'; 
    no warnings 'redefine'; 
    local *{'DateTime::today'} = sub { 
     return DateTime->new(
      year => 2012, 
      month => 5, 
      day => 31 
     ); 
    }; 
    say DateTime->today->ymd(); # 2012-05-31 
}; 

say DateTime->today->ymd(); # today