覆盖测试工具中的默认代码的优雅方式

时间:2010-09-24 04:38:58

标签: c++ unit-testing design-patterns

假设我有以下课程:

class Foo
{
public:
    Foo()
    {
        Bar();
    }

private:
    Bar(bool aSendPacket = true)
    {
        if (aSendPacket)
        {
            // Send packet...
        }
    }
};

我正在编写一个测试工具,需要通过工厂模式创建一个Foo对象(即我没有直接实例化它)。我无法更改任何工厂实例化代码,因为这是在我无法访问的框架中。

由于各种原因,我不希望Bar方法在从测试工具运行数据包时发送数据包。

假设我无法直接调用Bar(消除使用朋友类等潜在的解决方案),在运行我的测试工具时,用什么优雅的设计模式来防止数据包被发送?我绝对不想在特殊情况下污染我的生产代码。

3 个答案:

答案 0 :(得分:2)

您希望Bar在普通操作中发送数据包,但不在测试中。因此,您必须拥有一些代码,这些代码在测试期间调用Bar时运行,即使它是一个空函数。问题是在哪里提出来。

我们无法在if(aSendPacket)循环中看到代码,但如果它将其工作委托给其他类,那么我们可以在那里进行替换。也就是说,如果循环是

if(aSendPacket)
{
  mPacketHandler.send();
}

这样工作由`packetHandler class:

完成
// packetHandler.cc

void packetHandler::send()
{
  // do some things
  // send the packet
}

然后我们可以制作packetHandler类的“静音”版本。 (有些人会把它称为存根或模拟类,但似乎对这些术语的定义存在一些争议。)

// version of packetHandler.cc to be used when testing e.g. Foo

void packetHandler::send()
{
  // do some things
  // don't actually send the packet
}

在测试Foo时,请编译此版本的packetHandler并将其链接。工厂不会知道其中的差异。

另一方面,如果发送数据包的代码在Foo中拼写出来,无法阻止Foo类以外的行为,那么你必须要有一个{test}版本的Foo.cc(还有其他方法,但它们很笨拙和危险),最好的方法取决于你的代码库的细节。如果只有这样的“不可测试”特性,那么最好将Foo::bar(...)单独放入源文件中,使用两个版本(并对每个其他特殊方法执行相同操作)。如果有很多,那么可能值得推导出一个特定于测试的工厂类,它将构造例如class testingFoo : public Foo覆盖Bar。毕竟,这就是抽象工厂设计模式的用途。

答案 1 :(得分:0)

我会将'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'时使用)

答案 2 :(得分:0)

这可能是一个粗略的过度简化,但我的第一个倾向是添加某种测试条件对象(实际上是一个变量库),它将所有内容都默认为false,然后将钩子放在代码中,你想要偏离标准行为测试,切换[有效全局]测试条件对象变量。无论如何,你将需要做同等的逻辑,其他一切看起来要么更复杂,对于理解对象内部的逻辑流程更具破坏性,或者更可能破坏测试用例中的行为。如果您可以使用最少量的条件开关位置/变量,这可能是最简单的解决方案。

我的意见,无论如何。