CxxMock - principle of action


    Sometimes it is interesting to study the architecture of a product and see how it works. It happened that you’ll disassemble the clock, but you cannot assemble it back ... But unlike the clock, you can disassemble and assemble software products when accessing the sources. And the solutions found to apply already in their practice.

    When I had a need to create CxxMock , which I wrote about in the article CxxMock - Mock-objects in C ++ , I examined the principle of operation of a similar GoogleMock . Or even earlier, I figured out the basic idea of ​​the c10k server mathopd , which subsequent projects allowed me to better maneuver in the design of architecture.

    Therefore, I will talk about the basic concepts and due to which CxxMock works. And which were interesting to come up with. You may find some tricks simple, while others may help you with your practice.

    CxxMock inside look


    Interesting decisions about which there will be a speech:
    1. Imitation of behavior as if we have a reflection
    2. Registration of factories of facilities without overhead.
    3. Creating the desired implementation of the interface.
    4. Programming method behavior.
    5. Method execution control
    6. Comparison of arguments.
    7. Executing a custom method.


    Imitation of behavior as if we have a reflection


    In C # there is reflection, in C ++ there is not, but there is RTTI which can help identify types but can neither call methods nor build classes dynamically during program execution. That is, in order to create something, it is necessary that it already exists at compile time, and that the CxxMock kernel knows what needs to be created and how to create it. To achieve this, you can use the code parser in the same way as CxxTest does to build a table of contents for tests and Qt to create a QMetaOBject containing links to all signals and slots. In the case of CxxMock had to comply with the concept CxxTest and write a generator in python with all sorts of terrible regex parsing algorithm and brackets to account for such cases:

    namespace NS4 { namespace NS5 {
    class Interface
    {
    public:
    	 virtual void method(int a)=0;
    	 virtual Type* method2(const Type& a)=0;
    	 virtual ~Interface(){};
    };
    }}
    

    at the output, the generator creates a header file with a class (classes) that implements the interface
    namespace NS4 { namespace NS5 {
    class CXXMOCK_DECL( Interface )
    {
    public:
    	 virtual int method(int a) {
                 CXXMOCK( int, a ) 
             }
    	 virtual Type* method2(const Type& a){ 
                 return CXXMOCK( Type*, a ) 
             }
    	 virtual ~Interface(){};
    };
    CXXMOCK_IMPL( Interface )
    }}
    

    that is, quite readable code that can be written manually and which can be parsed by almost any syntax parser in the IDE. The first version did so, by writing such classes manually.

    Registration of factories without overhead


    If you approach the forehead, you would have for each class having a special implementation of the interface at some point setUp () or immediately write code like this:

    cxxmock::Repository::instance().registerFactory( new MockFactory() );
    cxxmock::Repository::instance().registerFactory( new MockFactory() );
    ...
    


    You must admit, it’s not very convenient to repeat the name of our interface ten times. Even if this piece of code is automatically created, where should it be inserted?

    We have limitations:
    1. In most cases, when using CxxTest, it is not necessary to manually rewrite the main () function. That is, we cannot insert our code into it.
    2. However, it is also not convenient to register in each setUp () method of each test set, even connecting the table of contents via the #include directive
    3. And it’s seemingly impossible to execute any code in the same place where our class is declared


    But when you can’t, but really want to, you can. A macro call is used for this purpose.
    CXXMOCK_IMPL( Interface )
    

    The macro is responsible for creating a static variable - a container typed by an interface and our created class - a stub
    #define CXXMOCK_IMPL( interface )  CxxMock::Container cxxmocks_instance_##interface;
    

    Due to the fact that in C ++, as well as in ANSI C, when loading a library into memory, even before starting main () and _init (), all static and global variables are initialized first. Since the variables declared in CXXMOCK_IMPL are an instance of the class, a constructor will be called for them, in which we can tell the registry that we have a container that can create objects that implement a specific interface.
    template 
    Container::Container()
    {
        Repository::instance().registerFactory( this );
    }
    

    The main purpose of the Container is to store information about the correspondence of an interface to its class stub, but it has a factory method for creating an instance of the class and is inherited from Factory.
    template 
    Interface* Container::create(){    return new Impl();  }
    

    therefore, they further refer to it as a factory.

    Of course, the order of initialization of global variables is not defined, but due to the use of lazy initialization of the container repository (factories), the order of creating global variables is not important for us. Thus, already at the beginning of the execution of a set of unit tests, CxxMock knows about all the classes that implement the interfaces that are required in unit tests.

    Creating the desired implementation of the interface


    In order to create something, you need to know WHAT to create and find someone who knows HOW to create what we need.
    In .NET, we can simply write:
    _registry[ typeof( factory ) ] = factory;
    

    for C ++ you need to apply magic with RTTI:

    template
    void Repository::registerFactory(Factory* factory)
    {
        string tname = typeid( typename Factory::TargetType ).name();
        _registry[ tname ] = factory;
    }
    

    _registry is a regular std :: mapUsing a pointer to Handle (base class for factories) provides dynamic_cast .

    after the factory is in the registry, we can ask the repository to create us an implementation of the stub object for our interface:

    template
    T* Repository::create()
    {
        RepositoryTypeMap::iterator it = _registry.find( typeid(T).name() );
        ...
        return dynamic_cast< Factory* >(&(*it->second))->create();
    }
    


    Here we apply the trick of casting the type from Handle back to Factory *, as in our collection are factories of completely different types that share only the Handle class.


    Programming Method Behavior


    Programming the behavior of the method is the most important part of CxxMock, because you must explicitly call the interface method so that the IDE highlights all the necessary arguments, but you need to tell CxxMock some parameters regarding this particular call.

    Ideally, it should look like this (Rhino.Mocks, C #):
    Expect.Call( mock->method( 5 ) ).returns( 10 );
    Expect.Call( ()=> { mock->voidMethod( 5 ); } ).repeat.any;
    


    In fact, two calls happen here:
    • First, our interface method mock-> method () is honestly called, then
    • The result, no matter what, is passed to the Expect.Call () call, which returns a CalllInfo structure containing the call information.

    Rhino.Mocks also uses the Expect class which `` '' knows `` '' about the current context and the active MockRepository.

    For the C ++ version, I applied a similar trick:
     TS_EXPECT_CALL( mock->method(10) ).returns(5);
    


    but using the macro TS_EXPECT_CALL with a compatible CxxTest signature in which the call was hidden:
     CxxMock::Repository::instance().expectCall(  mock->method(10) )
    

    the difference from Rhino.Mocks here is that in the first case, an additional class is not used to hide access to the repository instance (MocksRepository), and in the second there is the ability to mask the way the method () method is called.

    After the CallInfo structure is returned by reference from expectCall (), the usual work of setting up the object occurs.

    Passing arguments


    An interesting question with writing down the arguments with which the method was called and ensuring that the return value is stored and returned. Arguments must be stored and compared with them.

    CxxMock uses a mixed solution:

    1. The auto- generator creates a class using the CXXMOCK macro, which makes it easy to use in manual mode.

    int method(int a)
    {
       return CXXMOCK(int, a);
    }
    


    2. The CXXMOCK macro, in turn, calls the overloaded method cxxmock_object.mock (MOCK_FUNCID, args); which has an arbitrary typing similar to Action <> in C # (up to 10 arguments) and tells the CxxMock kernel a string representation of the method name. Since it is important for us to know exactly which method was called and what its signature is, and in C ++ it is possible to overload pure pure methods as well, registration by calling the full method signature is used using the MOCK_FUNCID macro implementing __PRETTY_FUNCTION__ or __FUNCDNAME__ depending about the compiler.

    template 
    R MockObject::mock( const std::string& funcname, A1 a1, A2 a2)
    {
       //Метод processCall() принимает решение: записывать действие или проверять вызов.
       return this->processCall(  method(funcname).arg( a1 ).arg( a2 ) );
    }
    CallInfo& MockObject::method( std::string funcname )
    {
       CallInfoPtr ptr = new CallInfo(funcname);
       Repository::instance().setLastCall( ptr );
       return *ptr;
    }
    

    Inside the overloaded method, all arguments are actually registered and the CallInfo structure is formed for further customization

    . Unlike the googlemock solution, here the MockObject :: mock () method for each call forms the same CallInfo structure into which all information about the call is written. The arguments to the method are saved in the same way as for the Class Factory .:
    template
    CallInfo& CallInfo::arg(const A value )
    {
    	inValues[ inValues.size() ] = new Argument(value);
    	return *this;
    }
    

    After that, in playback mode, a simple comparison of argument collections is performed using the unified IArgument interface for all Argument implementations.

    Comparison of Arguments


    Comparing the arguments is very simple. To do this, take the expected value and compare it with the value that came to the user interface call. And since there are many comparison options, here I just use the CxxTest features for comparing all types. He has good opportunities for this.

    template
    bool Argument::compare( const IArgument& other ) const
    {
       const Argument *arg = dynamic_cast< const Argument* >( &other );
       return CxxTest::equals( Value, arg->Value);
    }
    


    A more serious problem was getting a string representation of the value in order to generate a good error message like this:

    Expected a call: Interface :: method (5)
    Actually called: Interace :: method2 (6)

    Since the developer can use his data types, and if he uses some kind of framework for testing, then he should not write anything extra. Therefore, CxxTest is also applied here:
    template
    std::string convertToString(TVal arg ) const
    { 
        return CxxTest::ValueTraits( Value ).asString();  
    }
    const std::string toString() const
    {
        return convertToString( Value );
    }
    

    Doing these two things is the only place where integration with CxxTest is actually used.

    Custom method execution


    In order to execute a custom method, several conditions are necessary:
    1. It is necessary to save information about the method that needs to be called, and its type in order to correctly call it.
    2. We must equally invoke a user method call regardless of how many arguments it has.
    3. Given strict typing, you need to construct a method call as if we have a variable number of arguments.

    To save information about the method, just make a template class that stores a pointer to the function that we received at the input:
    template< typename Sender, typename T>
    CallInfo& action( Sender* sender, T method )
    {
        _action = new Action(sender, method );
        return *this;
    }
     

    In order to call “I don’t know what”, we apply a proxy method that implements an interface (IAction) but inside itself calls a template method that implements a specific strategy for calling a user method:
    template< typename Sender, typename T>
    class Action : public IAction
    {
       Sender* _sender;
       T _method;
    ...
       //шаблонный метод
       template
       void callMethod(IArgumentPtr result, const ArgList& args, R (Sender::*method)(A)){
          result->setValue(Argument((_sender->*_method)( args.val(0) )) );
       }
    ...
       void call(IArgumentPtr result, const ArgList& args)  {
          callMethod(result, args, _method );
       }
    }
    

    Due to the fact that in each template implementation we explicitly indicate the signature of the user method, at the point callMethod () our type T will be decomposed into type R (Sender :: * method) (A). which allows you to process a specific version of the call separately. And to construct the call to the user method in the same way as registering the call to the method.

    Due to the use of the solution “template inherited from the interface”, it is not required to create many service classes, it is enough to simply make many versions of methods with the implementation of the call strategy. This will simulate a method call with a variable number of arguments.

    Conclusion


    The main points of applied tricks:
    • Even a simple text can make an imitation of "reflection" when you really want to
    • You can execute any code before transferring control to main () using the constructor of the object
    • To bind template classes to a common point, you need to inherit the interface, dynamic_cast will do the rest as it should.
    • You can build sequence chains through the implicit use of the global context (Expect.Call (...)).
    • A dozen overloaded methods and collections of argument containers can make it easier to create your own version of the RPC or the task of comparing an argument list
    • No need to do everything yourself, sometimes the platform already provides opportunities for this
    • The complex type in the template can be decomposed into simpler types, which allows you to more accurately select the implementing method


    That's probably all the main tricks used in this simple CxxMock library, the main code of which takes only 15kb, but which allows to greatly simplify the life of the developer and IDE.

    Everything rests with
    SourceForge and GitHub .


    Thanks for attention.

    References


    1. CxxMock main site
    2. Mirror at SourceForge
    3. GitHub Mirror
    4. Cxxtest
    5. Rhino.Mocks
    6. Googlemock


    What to read


    To comprehend DAO programming, I also recommend:
    1. Myers Scott. Effective use of C ++. 55 Sure Ways to Improve the Structure and Code of Your Programs
    2. John Bentley Pearls of programming

    Also popular now: