Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/355.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
重写Java System.currentTimeMillis以测试时间敏感代码_Java_Testing_Jvm_Systemtime - Fatal编程技术网

重写Java System.currentTimeMillis以测试时间敏感代码

重写Java System.currentTimeMillis以测试时间敏感代码,java,testing,jvm,systemtime,Java,Testing,Jvm,Systemtime,除了手动更改主机上的系统时钟外,是否有一种方法可以通过代码或JVM参数覆盖当前时间(通过System.currentTimeMillis显示) 一点背景: 我们有一个系统,可以运行许多会计工作,这些工作的逻辑大部分围绕当前日期(即每月1日、每年1日等) 不幸的是,许多遗留代码调用函数,如newdate()或Calendar.getInstance(),这两个函数最终都会调用System.currentTimeMillis 出于测试目的,现在我们不得不手动更新系统时钟,以操纵代码认为测试正在运行的

除了手动更改主机上的系统时钟外,是否有一种方法可以通过代码或JVM参数覆盖当前时间(通过
System.currentTimeMillis
显示)

一点背景:

我们有一个系统,可以运行许多会计工作,这些工作的逻辑大部分围绕当前日期(即每月1日、每年1日等)

不幸的是,许多遗留代码调用函数,如
newdate()
Calendar.getInstance()
,这两个函数最终都会调用
System.currentTimeMillis

出于测试目的,现在我们不得不手动更新系统时钟,以操纵代码认为测试正在运行的时间和日期

所以我的问题是:

是否有方法覆盖系统返回的内容。currentTimeMillis?例如,告诉JVM在从该方法返回之前自动添加或减去某个偏移量


提前谢谢

在VM中确实没有直接执行此操作的方法,但是您可以在测试机器上通过编程方式设置系统时间。大多数(所有?)操作系统都有命令行命令来执行此操作。

我强烈建议您不要打乱系统时钟,而是咬紧牙关,重构旧代码以使用可替换的时钟。理想情况下,这应该通过依赖注入来实现,但即使使用可替换的单例,也可以获得可测试性

这几乎可以通过搜索和替换singleton版本实现自动化:

  • Calendar.getInstance()
    替换为
    Clock.getInstance().getCalendarInstance()
  • newDate()
    替换为
    Clock.getInstance().newDate()
  • System.currentTimeMillis()替换为
    Clock.getInstance().currentTimeMillis()
(根据需要等)

一旦迈出了第一步,您就可以一次用DI替换一点单例。

如下:

“使用Joda时间”几乎总是对任何涉及“如何使用java.util.Date/Calendar实现X”的问题的最佳答案

接下来(假设您刚刚将所有的
new Date()
替换为
new DateTime().toDate()


如果您想要导入一个具有接口的库(请参见下面Jon的评论),您可以使用,它将提供实现和标准接口。完整的jar只有96kB,所以它不应该破坏数据库…

使用面向方面编程(AOP,例如AspectJ)来编织系统类,以返回一个预定义的值,您可以在测试用例中设置该值

或者编织应用程序类,将调用重定向到
System.currentTimeMillis()
new Date()
到您自己的另一个实用程序类

但是,编织系统类(
java.lang.
)要稍微复杂一些,您可能需要为rt.jar执行脱机编织,并为测试使用单独的JDK/rt.jar


它被称为二进制编织,还有一些特殊的方法来执行系统类的编织并避免一些问题(例如,引导虚拟机可能不起作用)

效果很好。只是用它来模拟System.currentTimeMillis()

在我看来,只有非侵入性的解决方案才能奏效。特别是如果您有外部lib和大量遗留代码库,就没有可靠的方法来模拟时间

JMockit。。。仅适用于有限数量的

PowerMock&Co…需要模拟到System.currentTimeMillis()的系统。这也是一种侵入性的选择

从这里,我只看到提到的javaagent或aop方法对整个系统是透明的。有没有人这样做过,并能指出这样一个解决方案


@jarnbjo:您能展示一些javaagent代码吗?

虽然使用一些DateFactory模式看起来不错,但它不包括您无法控制的库-想象一下验证注释@Past,其实现依赖于System.currentTimeMillis(有这样的)

这就是为什么我们使用jmockit直接模拟系统时间:

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);
因为不可能达到原始未调整的毫秒值,所以我们改用nano定时器-这与挂钟无关,但相对时间在这里就足够了:

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}
有一个记录在案的问题,即HotSpot在多次呼叫后时间恢复正常-以下是问题报告:

要克服这一点,我们必须启用一个特定的热点优化—使用此参数运行JVM
-XX:-Inline

虽然这对于生产来说可能并不完美,但对于测试来说它是很好的,对于应用程序来说它是绝对透明的,特别是当DataFactory没有商业意义并且只是因为测试而引入时。如果有内置的JVM选项可以在不同的时间运行,那就太好了,但如果没有这样的攻击,这是不可能的

完整的故事在我的博客帖子中:

邮局提供了完整的方便类SystemTimeShift。类可以在测试中使用,也可以很容易地将它用作真正的主类之前的第一个主类,以便在不同的时间运行应用程序(甚至整个appserver)。当然,这主要是为了测试目的,而不是为了生产环境

编辑2014年7月:JMockit最近变化很大,您必须使用JMockit 1.0才能正确使用它(IIRC)。绝对不能升级到界面完全不同的最新版本。我在考虑只内联必要的东西,但由于我们在新项目中不需要这个东西,所以我根本不开发这个东西。

tl;博士 是否有一种方法,无论是在代码中还是在JVM参数中,都可以覆盖通过System.currentTimeMillis显示的当前时间,而不是手动覆盖
// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}
Instant.now( 
    Clock.fixed( 
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)
LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );
Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );
ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );
ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );
System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );
Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );
public static Instant now() {
    return Clock.systemUTC().instant();
}
public static ZonedDateTime now() {
    return now(Clock.systemDefaultZone());
}
import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { 
    initDefaultClock(); // initialisation in an instantiation block, but 
                        // it can be done in a constructor just as well
  }
  // (...)
}
import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot

    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method

    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }
# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar
[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"
@RunWith(PowerMockRunner.class)
@PrepareForTest(LegacyClass.class)
public class SystemTimeTest {
    
    private final Date fakeNow = Date.from(Instant.parse("2010-12-03T10:15:30.00Z"));

    @Before
    public void init() {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.currentTimeMillis()).thenReturn(fakeNow.getTime());
        System.out.println("Fake currentTimeMillis: " + System.currentTimeMillis());
    }

    @Test
    public void legacyClass() {
        new LegacyClass().methodWithCurrentTimeMillis();
    }

}
class LegacyClass {

    public void methodWithCurrentTimeMillis() {
        long now = System.currentTimeMillis();
        System.out.println("LegacyClass System.currentTimeMillis() is " + now);
    }

}
Fake currentTimeMillis: 1291371330000
LegacyClass System.currentTimeMillis() is 1291371330000