📜 ⬆️ ⬇️

CxxMock - principle of operation


Sometimes it is interesting to study the architecture of any product, and see how it works. That happened to disassemble the clock, and you can not collect back ... But in contrast to the clock software when accessing the source can be disassembled and assembled. And the solutions found are already used in their practice.

When I needed to create CxxMock , about which I wrote in the article CxxMock - Mock-objects in C ++ , I understood the principle of the similar GoogleMock . Or even earlier, I analyzed the main idea of ​​mathopd's c10k server, which in subsequent projects allowed me to better maneuver in architecture design.

Therefore, I will talk about the basic concepts and due to which CxxMock works. And that was interesting to invent. Some tricks may seem simple to you, while others may be able to help you in your practice.

CxxMock inside view


Interesting decisions that will be discussed:
  1. Imitation of behavior as if we have a reflection
  2. Ensuring registration of factories without an overhead.
  3. Creating the desired interface implementation.
  4. Programming method behavior.
  5. Control method
  6. Comparison of arguments.
  7. Execute custom method.

')

Imitation of behavior as if we have a reflection


In C # there is reflection, in C ++ there is no, 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 existed at compile time, and that the CxxMock kernel knows what to create and how to create it. To achieve this, you can use a code parser as well, as CxxTest does to build a test table of contents and Qt to create a QMetaOBject containing references to all signals and slots. In the case of CxxMock, I had to comply with the concept of CxxTest and write a generator in python with all sorts of scary regex and algorithm for parsing brackets to take into account such cases:

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

on output, the generator creates a header file with a class (s) 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 parse almost any syntax parser in the IDE. The first version was done by writing such classes manually.

Ensuring registration of factories without an overhead


If we approach the forehead, then for each class it would be necessary to have a special implementation of the interface at some point setUp () or immediately write code like this:

 cxxmock::Repository::instance().registerFactory<Interface>( new MockFactory<Interface, InterfaceImpl>() ); cxxmock::Repository::instance().registerFactory<Interface2>( new MockFactory<Interface2, Interface2Impl>() ); ... 


It agree, it is not so 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, you do not need to manually rewrite the main () function. That is, we cannot insert our code into it.
  2. However, it is also not convenient to do the registration in each setUp () method of each test suite, even connecting the table of contents through the #include directive
  3. And to execute some code in the same place where our class is declared is also seemingly impossible


But when it is impossible but really want - you can. The macro call is used for this purpose.
 CXXMOCK_IMPL( Interface ) 

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

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

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

therefore, it is further referred to as the Factory.

Of course, the order of initialization of global variables is not defined, but due to the use of lazy initialization of the repository of containers (factories), the order of creating global variables is not important to us. In this way, by the beginning of the execution of a set of unit tests, CxxMock knows about all the classes implementing the interfaces that will be required in the unit tests.

Creating the desired interface implementation


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 use magic with RTTI:

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

_registry is a regular std :: map <string, Handle *>, using a pointer to Handle (the base class for the factories) makes the dynamic_cast work.

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

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


Here we apply a trick with casting the type from Handle back to Factory *, as we have in the collection are factories of completely different types with only the general class Handle.


Programming method behavior


Programming the behavior of the method is the most important part of CxxMock, because it is necessary to explicitly call the interface method so that the IDE highlights all the necessary arguments, but at the same time you need to tell CxxMock any parameters related to 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 take place here:

Also in Rhino.Mocks, the Expect class is used which knows 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 TS_EXPECT_CALL macro with the CxxTest compatible signature that hid the call:
  CxxMock::Repository::instance().expectCall( mock->method(10) ) 

The difference from Rhino.Mocks here is that, firstly, there is no additional class used for hiding calls to the repository instance (MocksRepository), and secondly, it is possible to disguise the method of calling the method () method.

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

Passing arguments


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

Mixed solution is applied in CxxMock:

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 macro CXXMOCK, 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 signature it has, and in C ++ it is possible to overload including pure virtual methods, then registration by calling on the full signature of the method is used using the macro MOCK_FUNCID implementing __PRETTY_FUNC____ or __FUNCDNAME__ depending about the compiler.

 template <typename R, typename A1, typename A2> R MockObject::mock( const std::string& funcname, A1 a1, A2 a2) { // processCall()  :     . return this->processCall<R>( 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, the actual registration of all arguments takes place and a 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 the information about the call is recorded. The method arguments are saved in the same way as is done for the Class Factory .:
 template<typename A> CallInfo& CallInfo::arg(const A value ) { inValues[ inValues.size() ] = new Argument<A>(value); return *this; } 

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

Argument comparison


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

 template<typename T> bool Argument<T>::compare( const IArgument& other ) const { const Argument<T> *arg = dynamic_cast< const Argument<T>* >( &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:

Call Waiting: Interface :: method (5)
Actually called: Interace :: method2 (6)

Since the developer can use his own data types, and if he uses some kind of framework for testing, he should not write anything extra. Therefore, CxxTest is also used here:
 template<typename TVal> std::string convertToString(TVal arg ) const { return CxxTest::ValueTraits<T>( 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.

Run custom method


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

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

In order to call “I don’t know what”, we can use a proxy method that implements the interface (IAction) but within itself calls a template method that implements a specific strategy for calling a custom method:
 template< typename Sender, typename T> class Action : public IAction { Sender* _sender; T _method; ... //  template<typename R, typename A> void callMethod(IArgumentPtr result, const ArgList& args, R (Sender::*method)(A)){ result->setValue(Argument<R>((_sender->*_method)( args.val<A>(0) )) ); } ... void call(IArgumentPtr result, const ArgList& args) { callMethod(result, args, _method ); } } 

Due to the fact that we explicitly indicate the signature of the user method in each template implementation, at the point of callMethod () our type T will be decomposed into type R (Sender :: * method) (A). allowing you to handle a specific version of a call separately. And build a custom method call in the same way that the registration of the method call was performed.

Due to the use of the solution “template inherited from the interface”, it is not required to create many service classes, it is enough just to make many versions of the methods with the implementation of the calling strategy. That will provide a simulation of calling a method with a variable number of arguments.

Conclusion


The main theses of the applied tricks:


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

Everything lies on SourceForge and GitHub .


Thanks for attention.

Links


  1. CxxMock main site
  2. SourceForge Mirror
  3. Mirror on github
  4. CxxTest
  5. Rhino.Mocks
  6. GoogleMock


What to read


To understand 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 Programming gems

Source: https://habr.com/ru/post/251133/


All Articles