Record method calls in one session for replaying in future test sessions?

前端 未结 9 1286
梦毁少年i
梦毁少年i 2020-12-23 14:21

I have a backend system which we use a third-party Java API to access from our own applications. I can access the system as a normal user along with other users, but I do no

相关标签:
9条回答
  • 2020-12-23 14:24
    // pseudocode
    class LogMethod {
       List<String> parameters;
       String method;
       addCallTo(String method, List<String> params):
           this.method = method;
           parameters = params;
       }
    }
    

    Have a list of LogMethods and call new LogMethod().addCallTo() before every call in your test method.

    0 讨论(0)
  • 2020-12-23 14:26

    The idea of playing back the API calls sounds like a use case for the event sourcing pattern. Martin Fowler has a good article on it here. This is a nice pattern that records events as a sequence of objects which are then stored, you can then replay the sequence of events as required.

    There is an implementation of this pattern using Akka called Eventsourced, which may help you build the type of system you require.

    0 讨论(0)
  • 2020-12-23 14:28

    I should prefix this by saying I share some of the concerns in Yves Martin's answer: that such a system may prove frustrating to work with and ultimately less helpful than it would seem at first blush.

    That said, from a technical standpoint, this is an interesting problem, and I couldn't not take a go at it. I put together a gist to log method calls in a fairly general way. The CallLoggingProxy class defined there allows usage such as the following.

    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
    

    You could imagine wrapping your API object with CallLoggingProxy.create prior to passing it into your testing methods, capturing the data afterwards, and persisting it using whatever your favorite serialization system happens to be. Later, when you want to run your tests, you can load the data back up, create a new instance based on the data with CallLoggingProxy.replay, and passing that into your methods instead.

    The CallLoggingProxy is written using Javassist, as Java's native Proxy is limited to working against interfaces. This should cover the general use case, but there are a few limitations to keep in mind:

    • Classes declared final can't be proxied by this method. (Not easily fixable; this is a system limitation)
    • The gist assumes the same input to a method will always produce the same output. (More easily fixable; the ReplayInfo would need to keep track of sequences of calls for each input instead of single input/output pairs.)
    • The gist is not even remotely threadsafe (Fairly easily fixable; just requires a little thought and effort)

    Obviously the gist is simply a proof of concept, so it's also not been very thoroughly tested, but I believe the general principle is sound. It's also possible there's a more fully baked framework out there to achieve this sort of goal, but if such a thing does exist, I'm not aware of it.

    If you do decide to continue with the replay approach, then hopefully this will be enough to give you a possible direction to work in.

    0 讨论(0)
  • 2020-12-23 14:28

    If I understood you question correctly, you should try db4o.

    You will store the objects with db4o and restore later to mock and JUnit tests.

    0 讨论(0)
  • 2020-12-23 14:32

    you could look into 'Mockito'

    Example:

    //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));
    

    after you could replay each test more times and it will return data that you put in.

    0 讨论(0)
  • 2020-12-23 14:32

    I had a similar problem some years ago. None of the above solutions would have worked for methods that are not pure functions (side effect free). The major task is in my opinion:

    • how to extract a snapshot of the recorded object(s) (not only restricted to objects implementing Serializable)
    • how to generate test code of a serialized representation in a readable way (not only restricted to beans, primitives and collections)

    So I had to go my own way - with testrecorder.

    For example, given:

    ResultObject b = callBackend(a);
    
    ...
    
    ResultObject callBackend(SourceObject source) {
      ...
    }
    

    you will only have to annotate the method like this:

    @Recorded
    ResultObject callBackend(SourceObject source) {
      ...
    }
    

    and start your application (the one that should be recorded) with the testrecorder agent. Testrecorder will manage all tasks for you, such as:

    • serializing arguments, results, state of this, exceptions (complete object graph!)
    • finding a readable representation for object construction and object matching
    • generating a test from the serialized data
    • you can extend recordings to global variables, input and output with annotations

    An example for the test will look like this:

    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
    }
    
    0 讨论(0)
提交回复
热议问题