如何使用不断变化的系统时间测试Perl应用程序?

时间:2009-09-11 22:22:49

标签: perl datetime testing mocking

我有一个Web应用程序,我想要运行一些系统测试,为了做到这一点,我将需要移动系统时间。该应用程序一直使用DateTime。

有没有人对如何更改DateTime->现在报告的时间有任何建议?我唯一想到的是继承DateTime并搞乱所有'使用'线,但这看起来很有侵略性。

答案说明:

这三个都可以正常工作,但Hook :: LexWrap是我选择的那个因为(a)我想移动时钟而不是稍微摇晃它(这更像是Time ::的目的::模拟和朋友呢); (b)我一直使用DateTime,如果我不小心使用它,我很高兴出现错误; (c)Hook :: LexWrap比符号表中的hack更优雅,因为它做同样的事情。 (另外,它原来是我已安装的某些模块的依赖项,所以我甚至不用CPAN它...)

4 个答案:

答案 0 :(得分:11)

除了采用高级方法并专门包装DateTime之外,您可能需要查看模块Test::MockTimeTime::Mock,它们会覆盖{{{}}的低级函数。 1}}等使用,并且(运气好的话)会在任何时间敏感的代码上做正确的事情。对我而言,它似乎是一种更强大的测试方式。

答案 1 :(得分:4)

我认为Hook :: 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

如果您想在运行期间更改DateTime对当前时间的概念,您可以随时调整$offset

$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更改之类的内容会导致时间跳错到错误的伪时间。

答案 2 :(得分: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";
}

答案 3 :(得分:1)

在设计具有可测试性的新类时,理想的解决方案是能够注入新的日期对象。

但是,对于使用DateTime->nowDateTime->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