Let's say I have the following class:
class Foo
{
public:
Foo()
{
Bar();
}
private:
Bar(bool aSendPacket = true)
{
if (aSendPacket)
{
// Send packet...
}
}
};
I am writing a test harness which needs to create a Foo
object via the factory pattern (i.e. I am not instantiating it directly). I cannot change any of the factory instantiation code as this is in a framework which I don't have access to.
For various reasons I don't want the Bar
method to send packets when running it from a test harness.
Assuming I cannot call Bar
directly (eliminating potential solutions like using a friend class), what is an elegant design pattern to use to prevent packets being sent out when running my test harness? I definitely don't want to p开发者_Go百科ollute my production code with special cases.
You want Bar
to send a packet in ordinary operation, but not in testing. So you will have to have some code which runs when you call Bar
during testing, even if it's an empty function. The question is where to put it.
We can't see the code inside the if(aSendPacket)
loop, but if it delegates its work to some other class then we can make the substitution there. That is, if the loop is
if(aSendPacket)
{
mPacketHandler.send();
}
so that the work is done by the `packetHandler class:
// packetHandler.cc
void packetHandler::send()
{
// do some things
// send the packet
}
then we can make a "mute" version of the packetHandler
class. (Some would call it a stub or a mock class, but there seems to be somedebate about the definitions of these terms.)
// version of packetHandler.cc to be used when testing e.g. Foo
void packetHandler::send()
{
// do some things
// don't actually send the packet
}
When testing Foo
, compile this version of packetHandler
and link it in. The factory won't know the difference.
If, on the other hand, the code for sending a packet is spelled out in Foo
, with no way to head off the behavior outside the Foo
class, then you will have to have a "testing" version of Foo.cc
(there are other ways but they are clumsy and dangerous), and the best way to do that depends on the details of your codebase. If there are only a couple of "untestable" features like this, then it's probably best to put Foo::bar(...)
in a source file by itself, with two versions (and do the same for each of the other special methods). If there are many then may be worth deriving a factory class specific to testing, which will construct instances of, e.g. class testingFoo : public Foo
which overrides Bar
. After all, this is what the abstract factory design pattern is for.
I would view 'bar' as an algorithm to send data which follows a template method
// Automation Strategies
class AutomationStrategy{
public:
void PreprocessSend(bool &configsend) const {return doPreprocessSend(configsend);}
void PostprocessSend() const {return doPostprocessSend();}
virtual ~AutomationStrategy(){}
private:
virtual void doPreprocessSend(bool &configsend) const = 0;
virtual void doPostprocessSend() const = 0;
};
// Default strategy is 'do nothing'
class Automation1 : public AutomationStrategy{
public:
~Automation1(){}
private:
void doPreprocessSend(bool &configsend) const {}
void doPostprocessSend() const {}
};
// This strategy is 'not to send packets' (OP's intent)
class Automation2 : public AutomationStrategy{
public:
~Automation2(){}
private:
void doPreprocessSend(bool &configsend) const {
configsend = false;
}
void doPostprocessSend() const {}
};
class Foo{
public:
Foo(){
Bar();
}
private:
// Uses Template Method
void Bar(bool aSendPacket = true, AutomationStrategy const &ref = Automation1())
{
ref.PreprocessSend(aSendPacket); // Customizable Step1 of the algorithm
if (aSendPacket) // Customizable Step2 of the algorithm
{
// Send packet...
}
ref.PostprocessSend(); // Customizable Step3 of the algorithm
}
};
int main(){}
If you can't modify 'bar' interface, then configure 'Foo' to accept the test automation strategy in it's constructor and store it (to be later used while calling 'bar')
It might be a gross oversimplification, but my first inclination is to add some sort of testing conditions object (really a variable library) which defaults everything to false, then put hooks in the code where you want to deviate from standard behavior for testing, switching on the [effectively global] testing conditions object variables. You're going to need to do the equivalent logic anyway, and everything else seems either needlessly more complicated, more disruptive to understanding the logic flow inside the object, or more potentially disruptive to the behavior in the testing case. If you can get away with a minimal amount of conditional switch locations/variables, that probably the easiest solution.
My opinion, anyway.
精彩评论