We work with very old legacy system implemented in C++ with VC6 compiler. Now we are in the process of refactoring the code. We also switched to VC9 compiler.
We use an external proprietary framework, which is also legacy code and not unit testable. In order to make our code unit testable, we introduced interfaces and wrappers for the framework classes (hint: see “Working With Legacy Code” by Martin Fowler):
Now we depend on interfaces. The wrappers call the framework methods and we may happily use mocks in our unit tests.
And here we come to our problem...
The framework classes contain many methods that need to be wrapped and mocked. In order to achieve this goal, our supplier team wrote an API which generates interfaces, wrappers and mocks implementations with use of C++ Macros.
Example of wrapper header file:
class PlanWrapper : public IPlan
{
// ...
WRP_DECLARE_DEFAULTS(FrameworkPlan); // macro
WRP_DECLARE_CSTR_ATTR(FrameworkPlanLabel); // macro
// ...
};
The Macro WRP_DECLARE_CSTR_ATTR is defined like this:
#define WRP_DECLARE_CSTR_ATTR(AttrName) \
virtual bool set##AttrName (LPCTSTR Value_in); \
virtual bool get##AttrName (CString& Value_out); \
virtual bool unset##AttrName (); \
virtual bool isSet##AttrName ()
Example of wrapper cpp file:
#include "StdAfx.h"
using namespace SomeNamespace;
WRP_IMPLEMENT_MODDICOM_DEFAULTS(FrameworkPlan)
WRP_IMPLEMENT_W_CSTR_ATTR (FrameworkPlan,FrameworkType1, FrameworkPlanLabel)
// ...
The Macro WRP_IMPLEMENT_W_CSTR_ATTR is defined like this:
#define WRP_IMPLEMENT_W_CSTR_ATTR(ClassName,AtrTypeObj,AttrName) \
bool ClassName##Wrapper::set##AttrName (LPCTSTR Value_in) { \
AtrTypeObj aValue = Value_in; \
FrameworkLink<ClassName> convertedObj = NULL_LINK; \
framework_cast(convertedObj, m_Object); \
return convertedObj != NULL_LINK ? \
convertedObj->set##AttrName (aValue) : false; \
}
// ...
We have a bunch of even more complicated stuff, but I think you get the idea.
The problem with the API is that it is extremely complicated, not readable, not debuggable and not testable.
We would like to come up with a better mechanism for achieving the same goal. The idea was that we use some of the advanced features that came with new compiler like advanced templates, typelists, traits etc.
With templates we can almost achieve our goal, but we are stuck with the method names. We can generalize for types, but how do we deal with attribute names?
We also thought about creating a tool for automatically generating the wrapper + interfaces + mocks code. However, the API of our external framework is extremely complicated and writing such a tool would be very costly.
What do you think is the best way to solve such a problem? Maybe you already dealt with something like that and can provide good hints? We're looking forward to see your answers!
I think I would go with a code generation tool. I would probably make a few simple utility programs: one for generating an interface corresponding to a class of your legacy framework, one for generating the wrapper and one for generating the mock object (or at least a skeleton).
This implies having some way to parse the code of your legacy framework. I would have a look at Clang, or perhaps simply run ctags on the source file and treat the resulting tags.
Use Abstract factory instead macros to solve this problem.
class IApiFactory{
virtual ISomeApi1* getApi1() =0;
virtual ISomeApi2* getApi2() =0;
.....
};
After implement this interface for your Normal api and moc api and pass the instance of your factory to your system like:
MySystem system( new NormalApiFactory );
or
MySystem system( new MocApiFactory );
Your system must be declared as:
class MySystem{
public:
MySystem( IApiFactory* factory );
};
In your factory you will return the normal api implementation or moc objects. Of course you could return factories "which will return other factories or objects" from your root factory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With