1

我的 QA 团队正在进行业务生命周期测试(即老化、到期、到期、逾期等),这需要移动应用程序时钟。我可以更改所有代码以引用调整后的时钟(由我控制)。问题是(网络)应用程序使用了几个依赖于当前时间并通过或System.currentTimeMillis()直接或间接使用的第三方工具(例如 Spring Batch、Activiti 等)。DateCalendar

选项 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);

    }

}
4

0 回答 0