Introduction
After reading Michael Feathers' book Working Effectively with Legacy Code, I recently got interested in techniques for making code more testable. One of these techniques is Extract Interface, which can be used to replace objects of a certain class with fake versions. While trying to apply this to some legacy code, I stumbled upon a problem. This post describes that problem and how I solved it. However, I do have my doubts concerning the elegance of my solution. So therefore: feel free to comment and share alternative solutions!Starting point
Our starting point is a class derived from QObject:
Because this is a QObject
-derived class, it can use methods like setObjectName
and objectName
, but it can of course also have its own member functions like doFooStuff
. Our purpose now is to extract an interface for the Foo
class, so we can create a FakeFoo
class as a replacement for Foo
.
Extracting the interface
Faking the Foo
class seems easy at first sight: following Feathers' advice, all I should do is extract a FooInterface
and let my Foo
and FakeFoo
implement it. My first attempt in doing this, looked as follows:
Unfortunately, this resulted in the following compiler errors using g++ 4.9.2:
failed_attempt.cpp: In function ‘int main()’:
failed_attempt.cpp:29:14: error: ‘class FooInterface’ has no member named ‘setObjectName’
fakeFoo->setObjectName("fake foo");
^
failed_attempt.cpp:30:27: error: ‘class FooInterface’ has no member named ‘objectName’
std::cout << fakeFoo->objectName().toStdString() << std::endl;
^
failed_attempt.cpp:34:14: error: ‘class FooInterface’ has no member named ‘setObjectName’
realFoo->setObjectName("real foo");
^
failed_attempt.cpp:35:27: error: ‘class FooInterface’ has no member named ‘objectName’
std::cout << realFoo->objectName().toStdString() << std::endl;
Needless to say I was a bit disappointed in realizing that FooInterface
indeed did not have a setObjectName
or objectName
function. Inspired by the section The Case of the Onion Parameter and The Case of the Aliased Parameter in Feathers' book, my current best attempt so far in solving this issue consists of creating a QObjectInterface
with these methods and letting FooInterface
derive from that:
This works, as is visible in the output of the program:
fake foo
Foo::doFakeFooStuff()
real foo
Foo::doFooStuff()
But my current solution has several drawbacks:
- I need to add every
QObject
member function that I use, to myQObjectInterface
. - If I want
QObjectInterface
andFooInterface
to be real interfaces, that forces me to do the implementations of everyQObject
member function that I use in both myFoo
andFakeFoo
class. These implementations are just forwardings of method calls to theQObject
class. That is quite mechanical, cumbersome and errorprone. I don't like that.
Conclusion
I did find a way to fakeQObject
-derived classes, but my solution requires quite some brain-dead QObject
-method implementing in the original class and its fake version. I am open for more elegant designs. Please enlighten me! All code in this blog post is available in a public Gist.
Bart,
ReplyDeleteIk begrijp niet juist wat je wil doen en waarom, maar het onderstaande is misschien een oplossing voor je probleem.
template class FooInterface : public T { /* ... */ }
class FakeFoo : public FooInterface { /* ... */ }
class Foo : public FooInterface { /* ... */ }
FooInterface* fakeFoo = new FakeFoo();
Groetjes,
Ares
What about:
ReplyDeletetemplate
FooInterface createFoo(QString objectName) {
ConcreteFoo* foo = new ConcreteFoo();
foo->setObjectName(objectName);
return foo;
}
Usage:
FooInterface* fakeFoo = createFoo("fake foo");
Would http://doc.qt.io/qt-5/qobject.html#qobject_cast be any help? I.e., if you want to use a function belonging to the QObject interface, cast the FooInterface* to a QObject*.
ReplyDelete