Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/308.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 在一个会话中记录方法调用,以便在将来的测试会话中重播?_Java - Fatal编程技术网

Java 在一个会话中记录方法调用,以便在将来的测试会话中重播?

Java 在一个会话中记录方法调用,以便在将来的测试会话中重播?,java,Java,我有一个后端系统,我们使用第三方JavaAPI从我们自己的应用程序进行访问。我可以作为一个普通用户和其他用户一起访问系统,但我对它没有神圣的权力 因此,为了简化测试,我希望运行一个真正的会话并记录API调用,并将它们持久化(最好是作为可编辑代码),因此,我们可以稍后使用API调用进行干测试运行,只返回录制会话的相应响应——这是重要的部分——而无需与上述后端系统对话 因此,如果我的申请表中包含以下行: Object b = callBackend(a); 我希望框架首先捕获callBacken

我有一个后端系统,我们使用第三方JavaAPI从我们自己的应用程序进行访问。我可以作为一个普通用户和其他用户一起访问系统,但我对它没有神圣的权力

因此,为了简化测试,我希望运行一个真正的会话并记录API调用,并将它们持久化(最好是作为可编辑代码),因此,我们可以稍后使用API调用进行干测试运行,只返回录制会话的相应响应——这是重要的部分——而无需与上述后端系统对话

因此,如果我的申请表中包含以下行:

 Object b = callBackend(a);
我希望框架首先捕获
callBackend()
在给定参数a的情况下返回b,然后在以后的任何时候进行试运行时说“嘿,给定a,此调用应返回b”。a和b的值将是相同的(如果不是,我们将重新运行录制步骤)

我可以重写提供API的类,以便捕获的所有方法调用都将通过我的代码进行(即,不需要字节码插装来改变我控制之外的类的行为)

要做到这一点,我应该研究什么样的框架



编辑:请注意赏金猎人应该提供实际的代码来演示我所寻找的行为

如果我正确理解了您的问题,您应该试试db4o


您将使用db4o存储对象,并在稍后恢复到mock和JUnit测试。

几个月前,在规划大型应用程序的大规模技术重构时,我对非回归测试也有同样的需求,并且。。。我没有找到可用的框架

事实上,重放可能特别困难,并且可能只在特定的上下文中工作-没有(或很少)具有标准复杂性的应用程序可以真正被认为是无状态的。在使用关系数据库测试持久性代码时,这是一个常见问题。为了相关,必须恢复完整的系统初始状态,并且每个重播步骤必须以相同的方式影响全局状态。当一个系统状态被分为数据库、文件、内存等部分时,这就成为一个挑战。。。让我们猜测一下,如果某个地方使用了从系统时钟获取的时间戳,会发生什么

所以更实际的选择是只记录。。。然后为后续的运行做一个聪明的比较

根据您计划的运行次数,在应用程序上进行人工驱动的会话可能就足够了,或者您必须在机器人玩应用程序用户界面的自动化场景中进行投资

首先记录:您可以使用动态代理接口或方面编程来拦截方法调用,并捕获调用前后的状态。这可能意味着:转储相关的数据库表,复制一些文件,以XML等文本格式序列化Java对象

然后将此引用捕获与新运行进行比较。这种比较应该调整为从每个状态中排除任何不相关的元素,如行标识符、时间戳、文件名。。。仅比较后端增值显著的数据

最后,没有什么是真正标准的,通常只有几个特定的脚本和代码就足以实现目标:检测尽可能多的错误,并尝试防止非预期的副作用。

//pseudocode
// pseudocode
class LogMethod {
   List<String> parameters;
   String method;
   addCallTo(String method, List<String> params):
       this.method = method;
       parameters = params;
   }
}
类日志方法{ 列出参数; 字符串方法; addCallTo(字符串方法,列表参数): 这个方法=方法; 参数=参数; } }

在测试方法中的每次调用之前,都有一个
LogMethods
列表,并调用
newlogmethod().addCallTo()

在此之前,我应该说,我同意伊夫·马丁回答中的一些担忧:这样一个系统可能会令人沮丧,最终没有乍一看的那么有用

也就是说,从技术角度来看,这是一个有趣的问题,我不能不去尝试。我以一种相当通用的方式记录方法调用。这里定义的
CallLoggingProxy
类允许如下使用

Calendar original = CallLoggingProxy.create(Calendar.class, Calendar.getInstance());
original.getTimeInMillis(); // 1368311282470

CallLoggingProxy.ReplayInfo replayInfo = CallLoggingProxy.getReplayInfo(original);

// Persist the replay info to disk, serialize to a DB, whatever floats your boat.
// Come back and load it up later...

Calendar replay = CallLoggingProxy.replay(Calendar.class, replayInfo);
replay.getTimeInMillis(); // 1368311282470
您可以想象使用
CallLoggingProxy包装您的API对象。在将其传递到测试方法之前创建
,然后捕获数据,并使用您最喜欢的序列化系统将其持久化。稍后,当您想要运行测试时,可以加载数据备份,使用
CallLoggingProxy.replay
基于数据创建新实例,并将其传递到方法中

CallLoggingProxy
是使用编写的,因为Java的原生
Proxy
仅限于针对接口工作。这应该涵盖一般用例,但要记住一些限制:

  • 此方法无法代理声明为
    final
    的类。(不易修复;这是一个系统限制)
  • gist假设方法的相同输入总是产生相同的输出。(更容易修复;
    ReplayInfo
    需要跟踪每个输入的调用序列,而不是单个输入/输出对。)
  • 要点甚至都不是线程安全的(相当容易修复;只需要一点思考和努力)
很明显,要点只是一个概念证明,所以它也没有经过非常彻底的测试,但我相信一般原则是正确的。也有可能有一个更全面的框架来实现这类目标,但如果确实存在这样的东西,我不知道


如果您决定继续使用replay方法,那么希望这将足以为您提供一个可能的工作方向。

这可以通过面向方面编程的AOP来完成。它允许通过字节码操作拦截方法调用。搜索一些例子

在一种情况下,这可以进行录制,而在另一种情况下,这可以进行回放
public interface MyApi {
    public String getMySpouse(String myName);
    public int getMyAge(String myName);
    ...
}
    public class RecordedInformation {
       private String methodName;
       private Object[] args;
       private Object returnValue;

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public Object[] getArgs() {
            return args;
        }

        public void setArgs(Object[] args) {
            this.args = args;
        }

        public Object getReturnValue() {
            return returnType;
        }

        public void setReturnValue(Object returnValue) {
            this.returnValue = returnValue;
        }

        @Override
        public int hashCode() {
            return super.hashCode();  //change your implementation as you like!
        }

        @Override
        public boolean equals(Object obj) {
            return super.equals(obj);    //change your implementation as you like!
        }
    }
    public class RecordReplyManager implements java.lang.reflect.InvocationHandler {

        private Object objOfApi;
        private boolean isForRecording;

        public static Object newInstance(Object obj, boolean isForRecording) {

            return java.lang.reflect.Proxy.newProxyInstance(
                    obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(),
                    new RecordReplyManager(obj, isForRecording));
        }

        private RecordReplyManager(Object obj, boolean isForRecording) {
            this.objOfApi = obj;
            this.isForRecording = isForRecording;
        }


        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result;
            if (isForRecording) {
                try {
                    System.out.println("recording...");
                    System.out.println("method name: " + method.getName());
                    System.out.print("method arguments:");
                    for (Object arg : args) {
                        System.out.print(" " + arg);
                    }
                    System.out.println();
                    result = method.invoke(objOfApi, args);
                    System.out.println("result: " + result);
                    RecordedInformation recordedInformation = new RecordedInformation();
                    recordedInformation.setMethodName(method.getName());
                    recordedInformation.setArgs(args);
                    recordedInformation.setReturnValue(result);
                    //persist your information

                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw new RuntimeException("unexpected invocation exception: " +
                            e.getMessage());
                } finally {
                    // do nothing
                }
                return result;
            } else {
                try {
                    System.out.println("replying...");
                    System.out.println("method name: " + method.getName());
                    System.out.print("method arguments:");
                    for (Object arg : args) {
                        System.out.print(" " + arg);
                    }

                    RecordedInformation recordedInformation = new RecordedInformation();
                    recordedInformation.setMethodName(method.getName());
                    recordedInformation.setArgs(args);

                    //if your invocation information (this RecordedInformation) is found in the previously collected map, then return the returnValue from that RecordedInformation.
                    //if corresponding RecordedInformation does not exists then invoke the real method (like in recording step) and wrap the collected information into RecordedInformation and persist it as you like!

                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw new RuntimeException("unexpected invocation exception: " +
                            e.getMessage());
                } finally {
                    // do nothing
                }
                return result;
            }
        }
    }
    MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
    MyApi myApiWithRecorder = (MyApi) RecordReplyManager.newInstance(realApi, true); // true for recording
    myApiWithRecorder.getMySpouse("richard"); // to record getMySpouse
    myApiWithRecorder.getMyAge("parker"); // to record getMyAge
    ...
    MyApi realApi = new RealApi(); // using new or whatever way get your service implementation (API implementation)
    MyApi myApiWithReplayer = (MyApi) RecordReplyManager.newInstance(realApi, false); // false for replaying
    myApiWithReplayer.getMySpouse("richard"); // to replay getMySpouse
    myApiWithRecorder.getMyAge("parker"); // to replay getMyAge
    ...
//You can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);

//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

//following prints "first"
System.out.println(mockedList.get(0));

//following throws runtime exception
System.out.println(mockedList.get(1));

//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
ResultObject b = callBackend(a);

...

ResultObject callBackend(SourceObject source) {
  ...
}
@Recorded
ResultObject callBackend(SourceObject source) {
  ...
}
void testCallBackend() {
  //arrange
  SourceObject sourceObject1 = new SourceObject();
  sourceObject1.setState(...); // testrecorder can use setters but is not limited to them
  ... // setting up backend
  ... // setting up globals, mocking inputs

  //act
  ResultObject resultObject1 = backend.callBackend(sourceObject1);

  //assert
  assertThat(resultObject, new GenericMatcher() {
    ... // property matchers
  }.matching(ResultObject.class));
  ... // assertions on backend and sourceObject1 for potential side effects
  ... // assertions on outputs and globals
}