In which I explain why some coders are opposed to unit-testing with test-doubles due to the overhead of virtual functions, admit that they are sometimes right, and present a zero-overhead solution.
(Sorry if this posts looks like a lot of code. It’s not as bad as it looks though, the three examples are almost identical.)
It is not uncommon to find coders shying away from virtual functions due to the performance overhead. Often they are exaggerating, but some actually have the profiler data to prove it. Consider the following example, with a simulator class that calls the model heavily in an inner loop:
class Model { public: double getValue(size_t i); size_t size(); }; class Simulator { Model* model; public: Simulator() : model(new Model()) {} void inner_loop() { double values[model->size()]; while (simulate_more) { for (size_t i = 0; i < model->size(); ++i) { values[i] = model->getValue(i); } doStuffWith(values); } } };
Imagine that getValue()
is a light-weight function, light-weight enough that a virtual indirection would incur a noticeable performance-hit. Along comes the TDD-guy, preaching the values of unit testing, sticking virtual
all over the place to facilitate test doubles, and suddenly our simulator is slower than the competition.
Here’s how he would typically go about doing it (the modifications are marked with comments):
class Model { public: virtual double getValue(size_t i); //<--- virtual virtual size_t size(); }; class Simulator { Model* model; public: Simulator(Model* model) : model(model) {} //<--- inject dependency on Model void inner_loop() { double values[model->size()]; while (simulate_more) { for (size_t i = 0; i < model->size(); ++i) { values[i] = model->getValue(i); } doStuffWith(values); } } };
Now that the methods in Model
are virtual, and the Model
instance is passed in to the Simulator
constructor, it can be faked/mocked in a test, like this:
class FakeModel : public Model { public: virtual double getValue(size_t i); virtual size_t size(); }; void test_inner_loop() { FakeModel fakeModel; Simulator simulator(&fakeModel); //Do test }
Unfortunately, the nightly profiler build complains that our simulations now run slower than they used to. What to do?
The use of inheritance and dynamic polymorphism is actually not needed in this case. We know at compile time whether we will use a fake or a real Model
, so we can use static polymorphism, aka. templates:
class Model { public: double getValue(size_t i); //<--- look mom, no virtual! size_t size(); }; template <class ModelT> //<--- type of model as a template parameter class Simulator { ModelT* model; public: Simulator(ModelT* model) : model(model) {} //<--- Model is still injected void inner_loop() { double values[model->size()]; while (simulate_more) { for (size_t i = 0; i < model->size(); ++i) { values[i] = model->getValue(i); } doStuffWith(values); } } };
We have now parameterized Simulator
with the Model
type, and can decide at compile time which one to use, thus eliminating the need for virtual methods. We still need to inject the Model
instance though, to be able to insert the fake in the test like this:
class FakeModel { //<--- doesn't need to inherit from Model, only implement the methods used in inner_loop() double getValue(size_t i); size_t size(); }; void test_inner_loop() { FakeModel fakeModel; Simulator<FakeModel> simulator(&fakeModel); //Do test }
That’s it, all the benefits of test doubles, with zero performance overhead. There is a drawback however, in that we end up with templated code that wouldn’t need to be if it wasn’t for the purpose of testing. Not an ideal solution, but if both correctness and speed is important, and you have profiler data to prove the need, this is probably the way to go.
As usual, the sourcecode used in this post is available on GitHub.
If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter. Also, if you write code-heavy posts like this in vim and want to automate copying in code snippets from an external file, check out my vim-plugin SnippetySnip (also available on GitHub).