一般单元测试时总会有些代码会严重依赖系统当前时间,这种情况下的测试代码写起来可能就比较费劲,此处分享一种基于 ByteBuddy 的覆盖 System.currentTimeMillis
的解决方案。
ByteBuddy 是啥以及作用等详细介绍可以参照官网,此处直接上代码了:
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.8.22</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.8.22</version>
<scope>test</scope>
</dependency>
此处有两个注意点,一是系统方法被覆盖后咱们也拿不到当前时间了,这个问题基于 nanoTime 做一下手动计时,二是 classloader 的限制会拿不到成员变量,通过反射绕了一下
public static class SystemAdvice {
public static long startMill;
public static long startNano;
public static long offset;
@Advice.OnMethodExit
public static void currentTimeMillis(@Advice.Return(readOnly = false) long x) {
try {
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("com.*****$SystemAdvice");
x = clazz.getField("startMill").getLong(null)
+ (System.nanoTime() - clazz.getField("startNano").getLong(null)) / 1000000
+ clazz.getField("offset").getLong(null);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public static void resetTimeTo(Date date) {
AgentBuilder a = new AgentBuilder.Default()
.enableNativeMethodPrefix("wmsnative")
.with(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE))
.with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.ignore(ElementMatchers.none())
.type(ElementMatchers.named("java.lang.System"))
.transform((builder, typeDescription, classLoader, module) -> {
return builder.method(ElementMatchers.named("currentTimeMillis"))
.intercept(
Advice.to(SystemAdvice.class).wrap(StubMethod.INSTANCE)
);
});
Instrumentation inst = ByteBuddyAgent.install();
SystemAdvice.startMill = System.currentTimeMillis();
SystemAdvice.startNano = System.nanoTime();
a.installOn(inst);
SystemAdvice.offset = date.getTime() - SystemAdvice.startMill;
}
简单执行一下测试代码:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
resetTimeTo(sdf.parse("2021-11-11 23:23:23"));
for(int i = 0; i < 10; i ++) {
System.out.println(sdf.format(new Date()));
Thread.sleep(1000);
}
得到有序打印的时间序列:
2021-11-11 23:23:23
2021-11-11 23:23:24
2021-11-11 23:23:25
2021-11-11 23:23:26
2021-11-11 23:23:27
2021-11-11 23:23:28
2021-11-11 23:23:29
2021-11-11 23:23:30
2021-11-11 23:23:31
2021-11-11 23:23:32