C++ 覆盖测试线束中默认代码的优雅方式
假设我有以下课程:C++ 覆盖测试线束中默认代码的优雅方式,c++,unit-testing,design-patterns,C++,Unit Testing,Design Patterns,假设我有以下课程: class Foo { public: Foo() { Bar(); } private: Bar(bool aSendPacket = true) { if (aSendPacket) { // Send packet... } } }; 我正在编写一个测试工具,它需要通过工厂模式创建一个Foo对象(即,我没有直接实例化它)。我无法更改
class Foo
{
public:
Foo()
{
Bar();
}
private:
Bar(bool aSendPacket = true)
{
if (aSendPacket)
{
// Send packet...
}
}
};
我正在编写一个测试工具,它需要通过工厂模式创建一个Foo
对象(即,我没有直接实例化它)。我无法更改任何工厂实例化代码,因为这是在我无权访问的框架中
出于各种原因,我不希望从测试线束运行Bar
方法时发送数据包
假设我不能直接调用
Bar
(消除像使用friend类这样的潜在解决方案),那么在运行测试工具时,使用什么优雅的设计模式来防止数据包被发送出去呢?我绝对不想因为特殊情况而污染我的生产代码。我将“bar”视为一种按照模板方法发送数据的算法
// 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(){}
如果您不能修改“bar”接口,那么将“Foo”配置为在其构造函数中接受测试自动化策略并将其存储(稍后在调用“bar”时使用)这可能过于简单化,但我的第一个倾向是添加某种类型的测试条件对象(实际上是一个变量库)它将所有内容默认为false,然后将钩子放在代码中希望偏离标准测试行为的地方,打开[有效全局]测试条件对象变量。无论如何,您都需要执行等效的逻辑,而其他一切似乎都不必要地更加复杂,对理解对象内部的逻辑流更具破坏性,或者对测试用例中的行为更具潜在破坏性。如果您可以使用最少数量的条件开关位置/变量,那么这可能是最简单的解决方案
无论如何,我的意见是。您希望
Bar
在普通操作中发送数据包,而不是在测试中。因此,即使是空函数,在测试期间调用Bar
时也必须有一些代码运行。问题是把它放在哪里
我们看不到if(aSendPacket)
循环中的代码,但是如果它将其工作委托给其他类,那么我们可以在那里进行替换。也就是说,如果循环是
if(aSendPacket)
{
mPacketHandler.send();
}
因此,工作由` packetHandler类完成:
// packetHandler.cc
void packetHandler::send()
{
// do some things
// send the packet
}
然后我们可以创建packetHandler
类的“静音”版本。(有些人会称之为存根或模拟类,但关于这些术语的定义似乎存在一些争论。)
测试Foo
时,编译此版本的packetHandler
,并将其链接到。工厂不会知道有什么区别
另一方面,如果发送数据包的代码在
Foo
中详细说明,无法阻止Foo
类之外的行为,那么您将不得不拥有一个Foo.cc
的“测试”版本(还有其他方法,但它们既笨拙又危险),最好的方法取决于代码库的细节。如果只有两个这样的“不稳定”特性,那么最好将Foo::bar(…)
单独放在一个源文件中,有两个版本(并对其他每个特殊方法执行相同的操作)。如果有很多,那么可能值得派生一个特定于测试的工厂类,该类将构造的实例,例如,类testingFoo:public Foo
,它覆盖条
。毕竟,这就是抽象工厂设计模式的用途。您有权访问并可以更改“Foo”吗?@Chubsdad-是的,我绝对可以更改Foo
。将Foo放在facade后面,它将向Foo转发除Bar()函数外的所有函数调用。
// version of packetHandler.cc to be used when testing e.g. Foo
void packetHandler::send()
{
// do some things
// don't actually send the packet
}