我的 QA 团队正在进行业务生命周期测试(即老化、到期、到期、逾期等),这需要移动应用程序时钟。我可以更改所有代码以引用调整后的时钟(由我控制)。问题是(网络)应用程序使用了几个依赖于当前时间并通过或System.currentTimeMillis()
直接或间接使用的第三方工具(例如 Spring Batch、Activiti 等)。Date
Calendar
选项 1 - Spring AOP。当我尝试这个选项时,它似乎只检测 Spring 加载的 bean(?)因为System
类是在 Spring 框架之外加载的,所以它无法检测它。
选项 2 - JMockit。让 JMockit jar 超越 JUnit 有点不合常规。
选项 3 - 使用 Java 6 工具(选项 1 和选项 2 之间的共同部分)。回到基础......(在下面找到相关代码)。
但是,测试代码中的断言总是失败。
我在所有三个选项中都遇到了障碍。不敢相信以前没有人这样做过,但也找不到合理的解决方案。
提前致谢。
public class InstrumentationAgent {
private static Instrumentation instrumentation = null;
/**
* JVM hook to dynamically load InstrumentationAgent at runtime.
*
* The agent class may have an agentmain method for use when the agent is
* started after VM startup.
*
* @param agentArgument
* @param instrumentation
*/
public static void agentmain(String agentArgument, Instrumentation instrumentation) {
InstrumentationAgent.instrumentation = instrumentation;
}
/**
* Programmatic hook to dynamically load modified byte codes. This method initializes/load the agent if necessary.
*
* @param definitions
* @throws Exception
*/
public static void redefineClasses(ClassDefinition... definitions) throws Exception {
if (InstrumentationAgent.instrumentation == null) {
loadAgent();
}
InstrumentationAgent.instrumentation.redefineClasses(definitions);
}
private synchronized static void loadAgent() throws Exception {
if (InstrumentationAgent.instrumentation != null) {
return;
}
// Build the agent.jar file
final File jarFile = File.createTempFile("agent", ".jar");
jarFile.deleteOnExit();
final Manifest manifest = new Manifest();
final Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
mainAttributes.put(new Attributes.Name("Agent-Class"), InstrumentationAgent.class.getName());
mainAttributes.put(new Attributes.Name("Can-Retransform-Classes"), "true");
mainAttributes.put(new Attributes.Name("Can-Redefine-Classes"), "true");
final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile), manifest);
final JarEntry agent = new JarEntry(InstrumentationAgent.class.getName().replace('.', '/') + ".class");
jos.putNextEntry(agent);
final ClassPool pool = ClassPool.getDefault();
final CtClass ctClass = pool.get(InstrumentationAgent.class.getName());
jos.write(ctClass.toBytecode());
jos.closeEntry();
jos.close();
// Attach to VM and load the agent
VirtualMachine vm = VirtualMachine.attach(getPidFromRuntimeMBean());
vm.loadAgent(jarFile.getAbsolutePath());
vm.detach();
}
private static String getPidFromRuntimeMBean() throws Exception {
RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
Field jvmField = mxbean.getClass().getDeclaredField("jvm");
jvmField.setAccessible(true);
VMManagement management = (VMManagement) jvmField.get(mxbean);
Method method = management.getClass().getDeclaredMethod("getProcessId");
method.setAccessible(true);
Integer processId = (Integer) method.invoke(management);
return processId.toString();
}
}
public class SystemTimeInstrumentation {
private static long timeAdjustment = 200000L;
private static byte[] originalClassByteArray;
public static void startAdjustedClock() {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = null;
byte[] instrumentedClassByteArray = null;
try {
originalClassByteArray = pool.get(System.class.getName()).toBytecode();
ctClass = pool.makeClass(new java.io.ByteArrayInputStream(originalClassByteArray), false);
CtMethod ctMethod = ctClass.getDeclaredMethod("currentTimeMillis");
ctMethod.setBody("return 0L;");
instrumentedClassByteArray = ctClass.toBytecode();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CannotCompileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
try {
InstrumentationAgent.redefineClasses(new ClassDefinition[] { new ClassDefinition(System.class,
instrumentedClassByteArray) });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void stopAdjustedClock() {
if (originalClassByteArray == null) {
throw new RuntimeException("The stopAdjustedClock() called before startAdjustedClock()");
} else {
try {
InstrumentationAgent.redefineClasses(new ClassDefinition[] { new ClassDefinition(System.class,
originalClassByteArray) });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
originalClassByteArray = null;
}
}
public class SystemTimeInstrumentationTest extends TestCase {
@Test
public void testModifiedClock() throws Exception {
long unmodifiedTime = System.currentTimeMillis();
SystemTimeInstrumentation.startAdjustedClock();
long modifiedTime = System.currentTimeMillis();
SystemTimeInstrumentation.stopAdjustedClock();
assertTrue("The difference should me more than 200000", (modifiedTime-unmodifiedTime)>200000L);
}
}