Mild pickle. I have a project which has components that are difficult to test/mock. It might look something like this:
class Contenxt;
class Server : public SomeOtherClass
{
public:
ServerPlugin(const Contex&) {
a.registerCallback([this](int i){ handleData(i); });
}
void handleData(int i)
{
int x = b.someFunc(i);
}
int getX() const { return x; }
private:
ClassA a;
ClassB b;
int x{0};
};
For ServerPlugin::getX() to be tested, we need some type of control over at least ClassA to trigger the asynchronous process.
Obviously this is a simplification but should illustrate the issue. Note that while my class can be structured in just about any way, the main caveat is that I’m locked into that constructor and can’t get around that. My preferred method would be to have ClassA & ClassB be template parameters with a constructor that took a reference to each. My Unit test could use a test version of them w/ all functionality implemented. But, I can’t do that.
So AFAIK, I have the following options.
- Modify the test framework to allow Friend classes access to private members. (I’ve seen this done before but is obviously somewhat controversial. Only listing for completeness.)
- ClassA & ClassB could be protected members, and inherit the ServerPlugin object
- Expose ClassA & ClassB via a public accessor. (This seems pretty terrible.)
- Make ClassA & ClassB public. (Even more terrible)
So the good news is that the testing framework will test my class end to end just fine, but to me I’ve left out at least a good integration test, if not a unit-test. Also relying on a rather ominous end to end test runner makes for some slow development, and potentially daunting tasks for pinpointing the location of a bug… not to mention less flexibility with multiple inputs.
So, I’m Wondering if there’s something I’ve missed, or if anyone has an opinion either way.
6
You don’t have to unit-test everything. Relying on integration or E2E tests is fine, especially when the aspect you want to test is at the interface of two components. Here, you have the interface between your class, and the asynchronous process A.
Testing that A will trigger the correct method call is likely a waste of time: creating this test requires a lot of effort for fairly little value.
However, you can test that handleData()
works, with only minimal changes to your design: you just have to separate the callback registration from the rest of the code. For example:
class HandleDataForServerPlugin {
public:
void handleData(int i) {
x = b.someFunc(i);
}
int getX() const { return x; }
private:
ClassB b;
int x{0};
};
class ServerPlugin: public SomeOtherClass, private HandleDataForServerPlugin {
public:
ServerPlugin(const Context& ) {
a.registerCallback([this](int i) { handleData(i); })
}
// re-export some methods
using HandleDataForServerPlugin::getX;
private:
ClassA a;
};
You can now test HandleDataForPlugin
directly, without a dependency on the ClassA
trigger. Whether the dependency from the server plugin to ClassB
is a problem depends on context, here I’ll assume that using this class is an internal implementation detail of the handleData()
logic.
Separating a class into an easy-to-test layer and a layer that adapts it to some public API is a pattern I’ve used occasionally when directly testing through the public API would be too cumbersome. In C++, you can also do this without exposing the lower layer publicly, unless you’re implementing a header-only library. Additionally, splitting your classes like this is a zero-cost abstraction in C++.
If this separation is not possible, consider whether your ClassA and ClassB dependencies could not be changed to use an interface, and injected. You can still leave the single-parameter constructor intact.
class ServerPlugin: public SomeOtherClass {
public:
ServerPlugin(const Context& ctx)
: ServerPlugin(ctx, std::make_unique<ClassA>(), std::make_unique<ClassB>()) {}
ServerPlugin(const Context&,
std::unique_ptr<InterfaceA> a,
std::unique_ptr<InterfaceB> b)
: a{a}, b{b} {
a.registerCallback([this](int i) { handleData(i); });
}
void handleData(int i) {
x = b.someFunc(i);
}
int getX() const { return x; }
private:
std::unique_ptr<InterfaceA> a;
std::unique_ptr<InterfaceB> b;
int x{0};
};
Dependency injection (here, as constructor injection) is the typical way to solve this kind of issue in other OOP languages. C++ just makes this more difficult because virtual methods are not the default, so the ability to inject a mock implementation for ClassB has to be an upfront design goal.
You will not be able to sensibly test all code paths (the path through the single-parameter constructor). But that path is not very risky since you only select the default ClassA and ClassB types.
2
I agree with what someone said that you should not try to test communications interfaces, if you really need it, then use Functional testing.
Otherwise if you really need to unit test it, then you should decouple dependencies by using dependency injection techniques in the constructor (constructor injecion). You can use boost DI or a service locator pattern.
The important part is that you should be able to mock the dependencies you are not testing, and for that you can use a base class that is a pure virtual.