代码之家  ›  专栏  ›  技术社区  ›  LeopardSkinPillBoxHat

重写测试线束中默认代码的优雅方法

  •  0
  • LeopardSkinPillBoxHat  · 技术社区  · 14 年前

    假设我有以下课程:

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

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

    因为种种原因我不想 Bar 方法在从测试线束运行数据包时发送数据包。

    假设我不能打电话 酒吧 直接(消除潜在的解决方案,比如使用friend类),在运行我的测试工具时,使用什么优雅的设计模式来防止数据包被发送出去?我绝对不想在特殊情况下污染我的产品代码。

    3 回复  |  直到 14 年前
        1
  •  2
  •   Beta    14 年前

    你想要什么 Bar 在普通操作中发送数据包,但不在测试中。所以你必须 一些 调用时运行的代码 酒吧 在测试过程中,即使它是一个空函数。问题是把它放在哪里。

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

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

    // 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 ,编译此版本的 把它连起来。工厂不会知道区别的。

    另一方面,如果发送数据包的代码是用 ,无法阻止外界的行为 类,则必须有一个 Foo.cc (还有其他方法,但它们既笨拙又危险),最好的方法取决于代码库的细节。如果只有这样几个“不稳定”的特性,那么最好 Foo::bar(...) 在一个源文件中,使用两个版本(并对其他每个特殊方法执行相同的操作)。如果有很多,那么可能值得派生一个特定于测试的工厂类,它将构造的实例,例如。 class testingFoo : public Foo 哪些覆盖 酒吧 . 毕竟,这就是抽象工厂设计模式的目的。

        2
  •  0
  •   Chubsdad    14 年前

    // 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'时使用)

        3
  •  0
  •   Nick    14 年前

    这可能过于简单化了,但我的第一个倾向是添加某种类型的testing conditions对象(实际上是一个变量库),它将所有内容都默认为false,然后在代码中放置钩子,在其中您希望偏离测试的标准行为,打开[effective global]testing conditions对象变量。不管怎样,您都需要执行等价的逻辑,其他的一切看起来要么不必要地更加复杂,对理解对象内部的逻辑流更具破坏性,要么对测试用例中的行为更具潜在破坏性。如果你能用最少的条件开关位置/变量,那可能是最简单的解决方案。