Mik*_*ark 117 java testing jvm systemtime
有没有办法,无论是在代码中还是在JVM参数中,都可以覆盖当前时间,如System.currentTimeMillis手动更改主机上的系统时钟所示?
一点背景:
我们有一个系统可以运行许多会计工作,这些工作围绕当前日期(即本月的第一天,一年中的第一天等)展开大部分逻辑.
不幸的是,很多遗留代码都会调用诸如new Date()or之类的函数,这些函数Calendar.getInstance()最终都会调用System.currentTimeMillis.
出于测试目的,我们现在仍然需要手动更新系统时钟来操作代码认为运行测试的时间和日期.
所以我的问题是:
有没有办法覆盖返回的内容System.currentTimeMillis?例如,告诉JVM在从该方法返回之前自动添加或减去一些偏移量?
提前致谢!
Jon*_*eet 109
我强烈建议您不要乱用系统时钟,而是重复使用可更换的时钟来重构旧代码.理想情况下应该使用依赖注入,但即使您使用了可替换的单例,也可以获得可测试性.
这可以通过搜索和替换单例版本几乎自动化:
Calendar.getInstance()为Clock.getInstance().getCalendarInstance().new Date()为Clock.getInstance().newDate()System.currentTimeMillis()为Clock.getInstance().currentTimeMillis()(等等)
一旦你完成了第一步,你可以一次用DI替换单身.
Bas*_*que 66
有没有办法在代码或JVM参数中覆盖当前时间,如System.currentTimeMillis所示,而不是手动更改主机上的系统时钟?
是.
Instant.now(
Clock.fixed(
Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
)
)
Run Code Online (Sandbox Code Playgroud)
Clock 在java.time中我们有一个新的解决方案来解决可插拔时钟更换的问题,以便于使用虚假的日期时间值进行测试.该java.time包中的Java 8包含一个抽象类java.time.Clock,有明确的目的:
允许在需要时插入备用时钟
您可以插入自己的实现Clock,但您可能会找到一个已满足您需求的实现.为方便起见,java.time包含静态方法以产生特殊实现.这些替代实施在测试期间可能是有价值的
各种 tick…方法产生的时钟以不同的节奏增加当前时刻.
默认情况下,Java 8和Java 9中Clock的时间报告频率为毫秒,精确到纳秒 (取决于您的硬件).您可以要求以不同的粒度报告真实的当前时刻.
tickSeconds - 整秒的增量tickMinutes - 整个分钟的增量tick- 通过Duration参数的增量.某些时钟可能存在,产生的结果与主机OS的硬件时钟不同.
例如,锁定今年最早圣诞节的第一时刻.换句话说,当圣诞老人和他的驯鹿第一次停下来时.最早的时区现在似乎是Pacific/Kiritimati在+14:00.
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 );
Run Code Online (Sandbox Code Playgroud)
使用该特殊固定时钟始终返回相同的时刻.我们在Kiritimati获得了圣诞节的第一个时刻,UTC显示的是12月24日前一天早上十四点钟的挂钟时间.
![]()
Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );
Run Code Online (Sandbox Code Playgroud)
instant.toString():2016-12-24T10:00:00Z
zdt.toString():2016-12-25T00:00 + 14:00 [太平洋/ Kiritimati]
请参阅IdeOne.com中的实时代码.
您可以控制Clock实施分配的时区.这在某些测试中可能很有用.但我不建议在生产代码中使用它,您应该始终明确指定可选ZoneId或ZoneOffset参数.
您可以指定UTC是默认区域.
ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );
Run Code Online (Sandbox Code Playgroud)
您可以指定任何特定时区.指定适当的时区名称,格式continent/region,如America/Montreal,Africa/Casablanca或Pacific/Auckland.切勿使用3-4字母缩写,例如EST或IST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!).
ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Run Code Online (Sandbox Code Playgroud)
您可以指定JVM的当前默认时区应该是特定Clock对象的默认时区.
ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );
Run Code Online (Sandbox Code Playgroud)
运行此代码进行比较.请注意,它们都报告了相同的时刻,即时间轴上的相同点.它们的区别仅在于挂钟时间 ; 换句话说,三种说法相同的方式,三种方式来显示同一时刻.
System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );
Run Code Online (Sandbox Code Playgroud)
America/Los_Angeles 是运行此代码的计算机上的JVM当前默认区域.
zdtClockSystemUTC.toString():2016-12-31T20:52:39.688Z
zdtClockSystem.toString():2016-12-31T15:52:39.750-05:00 [美国/蒙特利尔]
zdtClockSystemDefaultZone.toString():2016-12-31T12:52:39.762-08:00 [America/Los_Angeles]
根据Instant定义,该类始终为UTC.所以这三个与区域相关的Clock用法具有完全相同的效果.
Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );
Run Code Online (Sandbox Code Playgroud)
instantClockSystemUTC.toString():2016-12-31T20:52:39.763Z
instantClockSystem.toString():2016-12-31T20:52:39.763Z
instantClockSystemDefaultZone.toString():2016-12-31T20:52:39.763Z
默认使用的实现Instant.now是返回的实现Clock.systemUTC().这是您未指定a时使用的实现Clock.请参阅预发布的Java 9源代码Instant.now.
public static Instant now() {
return Clock.systemUTC().instant();
}
Run Code Online (Sandbox Code Playgroud)
默认Clock的OffsetDateTime.now和ZonedDateTime.now是Clock.systemDefaultZone().请参阅源代码.
public static ZonedDateTime now() {
return now(Clock.systemDefaultZone());
}
Run Code Online (Sandbox Code Playgroud)
Java 8和Java 9之间默认实现的行为发生了变化.在Java 8中,尽管类具有存储分辨率为纳秒的能力,但当前时刻仅以毫秒为单位捕获.Java 9带来了一种新的实现方式,能够以纳秒的分辨率捕获当前时刻 - 当然,这取决于计算机硬件时钟的能力.
该java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.
现在处于维护模式的Joda-Time项目建议迁移到java.time类.
要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.
您可以直接与数据库交换java.time对象.使用符合JDBC 4.2或更高版本的JDBC驱动程序.不需要字符串,不需要课程.java.sql.*
从哪里获取java.time类?
该ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.
Ste*_*hen 42
正如Jon Skeet所说:
"使用Joda Time"对于任何涉及"如何使用java.util.Date/Calendar实现X?"的问题几乎总是最佳答案.
所以这里(假设你刚刚更换了所有你new Date()的new DateTime().toDate())
//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();
Run Code Online (Sandbox Code Playgroud)
如果你想导入一个有接口的库(参见下面的Jon的评论),你可以使用Prevayler的Clock,它将提供实现以及标准接口.满罐只有96kB,所以它不应该破坏银行......
vir*_*o47 15
虽然使用一些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);
Run Code Online (Sandbox Code Playgroud)
因为不可能达到millis的原始未锁定值,我们使用纳米计时器 - 这与挂钟无关,但相对时间足够:
// 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;
}
Run Code Online (Sandbox Code Playgroud)
有记录的问题,使用HotSpot,经过多次调用后,时间恢复正常 - 这是问题报告:http://code.google.com/p/jmockit/issues/detail?id = 43
为了解决这个问题,我们必须打开一个特定的HotSpot优化 - 使用此参数运行JVM -XX:-Inline.
虽然这对于生产来说可能并不完美,但它对于测试来说还是很好的,它对应用程序来说绝对透明,特别是当DataFactory没有商业意义并且仅仅是因为测试而引入时.有内置的JVM选项在不同的时间运行会很不错,太糟糕了,没有像这样的黑客攻击是不可能的.
完整的故事在我的博客文章中:http: //virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
帖子中提供了完整的便捷类SystemTimeShifter.类可以在您的测试中使用,或者可以非常轻松地在真正的主类之前用作第一个主类,以便在不同的时间运行您的应用程序(甚至整个应用程序服务器).当然,这主要用于测试目的,而不是用于生产环境.
编辑2014年7月:JMockit最近发生了很大的变化,你必然会使用JMockit 1.0来正确使用它(IIRC).绝对无法升级到界面完全不同的最新版本.我正在考虑内联必要的东西,但是因为我们在新项目中不需要这个东西,所以我根本就没有开发这个东西.
您需要执行以下操作:
java.time.Clock向测试类添加一个新属性,MyService并确保使用实例化块或构造函数在默认值下正确初始化新属性:
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
}
// (...)
}
Run Code Online (Sandbox Code Playgroud)
将新属性clock注入到调用当前日期时间的方法中。例如,在我的情况下,我必须检查存储在 dataase 中的日期是否发生在 之前LocalDateTime.now(),我将其替换为LocalDateTime.now(clock),如下所示:
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();
}
}
// (...)
}
Run Code Online (Sandbox Code Playgroud)
在测试类中,创建一个模拟时钟对象,并在调用测试方法之前将其注入到测试类的实例中doExecute(),然后立即将其重置,如下所示:
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(..)
}
}
Run Code Online (Sandbox Code Playgroud)
在调试模式下检查它,您将看到 2017 年 2 月 3 日的日期已正确注入myService实例并用于比较指令,然后已正确重置为当前日期initDefaultClock()。
| 归档时间: |
|
| 查看次数: |
74421 次 |
| 最近记录: |