Элегантный способ переопределения кода по умолчанию в тестовом жгуте - PullRequest
0 голосов
/ 24 сентября 2010

Допустим, у меня есть следующий класс:

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

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

Я пишу тестовый жгут, который должен создать объект Foo через фабричный шаблон (т.е. я не создаю его экземпляр напрямую). Я не могу изменить какой-либо заводской код реализации, так как он находится в среде, к которой у меня нет доступа.

По разным причинам я не хочу, чтобы метод Bar отправлял пакеты при запуске из тестового жгута.

Предполагая, что я не могу вызвать Bar напрямую (исключая потенциальные решения, такие как использование класса друга), какой элегантный шаблон проектирования использовать для предотвращения отправки пакетов при запуске моего тестового комплекта? Я определенно не хочу загрязнять свой производственный код особыми случаями.

Ответы [ 3 ]

2 голосов
/ 24 сентября 2010

Вы хотите, чтобы Bar отправлял пакет в обычной работе, но не в тестировании. Таким образом, вам понадобится некоторый код, который запускается при вызове Bar во время тестирования, даже если это пустая функция. Вопрос в том, где его поставить.

Мы не можем видеть код внутри цикла if(aSendPacket), но если он делегирует свою работу какому-то другому классу, тогда мы можем сделать замену там. То есть, если цикл равен

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

, чтобы работа выполнялась классом `packetHandler:

// 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, то вам понадобится «тестовая» версия из Foo.cc (есть другие способы, но они неуклюжи и опасны), и лучший способ сделать это зависит от деталей вашей кодовой базы. Если есть только пара «непроверяемых» функций, подобных этой, то, вероятно, лучше всего поместить Foo::bar(...) в исходный файл отдельно, с двумя версиями (и сделать то же самое для каждого из других специальных методов). Если их много, то может быть целесообразно получить фабричный класс, специфичный для тестирования, который будет создавать экземпляры, например, class testingFoo : public Foo который переопределяет Bar. В конце концов, это то, для чего предназначен абстрактный шаблон фабричного дизайна.

0 голосов
/ 24 сентября 2010

Это может быть грубым упрощением, но я в первую очередь хочу добавить какой-нибудь объект условий тестирования (на самом деле, библиотеку переменных), который по умолчанию все имеет значение false, а затем поместить хуки в код, где вы хотите отклониться от стандартного поведениятестирование, включение [эффективно глобальных] переменных объекта условий тестирования.В любом случае вам потребуется выполнить эквивалентную логику, а все остальное кажется либо излишне сложным, либо более разрушительным для понимания логического потока внутри объекта, либо более потенциально разрушительным для поведения в тестовом случае.Если вам удастся избежать минимального количества условных переключателей / переменных, это, вероятно, самое простое решение.

В любом случае, на мой взгляд.

0 голосов
/ 24 сентября 2010

Я бы рассматривал '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')

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...