Reputation: 1145
I'm trying to bring some C++ legacy code under test. In particular, I have a class hierarchy, say, A < B < C
(i.e., A
is a subclass of B
, and B
is a subclass of C
), and there is a global reference to an object of type C
which is used from all over the system's code (singleton pattern). The goal is to replace that C
object with some fake object (in fact, C
is used to access a database).
My first attempt was to introduce interfaces IA, IB, and IC
(which contain pure virtual versions of the functions of the corresponding class), let each class implement its interface, and change the type of the global C
reference to IC
. In the setup function of my tests, I would then replace the globally referenced C
object with my own implementation of IC
, making the whole system use my fake implementation.
However, classes A, B
, and C
each contain quite a few non-virtual functions. Now, if I would make the classes inherit from my interfaces, I would change the semantics of these functions from non-virtual to virtual (Feathers discusses this problem in "Working efficiently with legacy code", p.367). In other words: I have to check each and every call to my global object, and I have to make sure that after my changes, still the same functions are called. This sounds like a LOT of ERROR PRONE work to me.
I also thought about making the non-virtual functions "final", i.e., tell the compiler that the functions of A, B
and C
must not be hidden in subclasses (which would make the compiler tell me all potentially dangerous functions of B
and C
- if a function is not hidden in a base class, the above effect can not happen at all), but that doesn't seem to be supported by C++ (we are not yet using C++11, but even its final keyword only seems to be applicable to virtual functions).
To make the situation even more difficult, classes A, B
, and C
also contain public attributes, virtual functions, and also some template functions.
So my question is: How to cope with the situation I described above? Are there any C++ capabilities that I have missed, and which could help in my scenario? Any design patterns? Or even any refactoring tools? My main requirement is that the changes must be as safe as possible, since the classes I'd like to fake are rather crucial to the system... I would also be happy with an "ugly" solution which would allow me to put tests into place (and which could be refactored later if the system is appropriately covered with tests).
Edit: I had messed up my inheritance hierarchy (it was upside down) - this is corrected now.
Edit2: We have finally ended up as follows: We have made only the functions virtual that we actually needed for our current test cases. We then checked each call to those methods (which was manageable). This then allowed us to mock our class using Google Mocks. Having more and more test cases in place, the hope is that our changes get saver by time. Note that when asking my question, I thought that Google Mocks could only mock pure interfaces; this is not the case, thus allowing for an incremental approach as described above.
Upvotes: 3
Views: 1831
Reputation: 96241
I'm going to suggest changing C
into a template, where the template type is the actual database access/implementation code. Then in your live program you use C<LiveDatabase>
everywhere you currently use C
and when testing you use C<MockDatabase>
. Then A
and B
remain unchanged, and all the pieces of C
's inner workings remain the same with only the specific database calls differing (in their calls to the delegated implementation).
Next I would go in and work on the public attributes, as they will only cause you headaches and hard-to-find bugs. Just flat out make them all private and let your compiler tell you were they're being accessed. For read-only use it's super easy to just add accessors. In places where they're being mutated you'll need to determine the appropriate interface to your class to achieve the desired mutation. Don't just add a mutator method unless that's absolutely your last alternative.
Upvotes: 2
Reputation: 3375
EDIT: Per Dietmar Kühl's comment below, this approach won't work to fake the member function templates.
I'm not sure if this will work, but try making a fake class (non virtual) in a separate .cpp file. Then, link the tests with the fake class, but not the real one. In theory the two shojld have the same ABI (Application Binary Interface), so the two should be compatible.
Upvotes: 1