Тестирование класса, который зависит от статических функций другого класса - PullRequest
3 голосов
/ 02 ноября 2010

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

Все работало нормально, пока я не попробовал протестировать свой класс.

Вот простой пример кода проблемы:

class A {
    static String getSometing() {
        return String("Something: ") + heavyCalculation().asString();
    }
}

class B {
    B() {}
    ~B() {}
    String runSomething(const String& name) {
        if(name.equals("something")) {
            return A::getSomething();
        } else {
            return "Invalid name!";
        }
    }
}

Предполагая, что класс A работает правильно (и был протестирован его модульными тестами), я хотел бы проверить функцию runSomething в классе B.

Моим первым вариантом было бы создание макетов для внутренних классов (в этом примере - класс A), но в этом случае он не даст мне ничего унаследовать от A, потому что он имеет только статические функции.

Мой второй вариант заключается в том, чтобы инкапсулировать вызовы класса A в частные функции внутри B, чтобы я мог контролировать их возвращаемые значения (хотя выбор этой опции немного усложнит задачу).

Мой вопрос к вам: Существуют ли лучшие способы тестирования классов C ++, которые зависят от статических классов / функций, чем мои текущие параметры?

Заранее спасибо,

Tal.

Ответы [ 6 ]

3 голосов
/ 02 ноября 2010

У меня был успех в модульном тестировании в подобных ситуациях, благодаря рефакторизации ссылок на статические функции (в моем случае это были внешние зависимости) в новые частные функции и перезаписи их на тестируемой заглушке, поэтому я могу рекомендовать этот подход

Если переработанные функции остаются закрытыми, они не должны сильно влиять на сложность проекта, и они должны быть достаточно маленькими, чтобы не оказывать негативного влияния на читаемость кода.

1 голос
/ 02 ноября 2010

Если вы не используете монолитный набор тестов, то это просто. Я предполагаю, что у вас есть класс A в A.cpp и класс B в B.cpp, а тесты для B находятся в B_test.cpp.

Создайте файл с именем A_mock.cpp

class A
{
    static String getSometing() {
        return String("Expected Something");
    }
};

Затем при компиляции вашего файла B_test просто свяжите с A_mock.o, а не с A.o.

g++ -Wall B_test.cpp B.cpp A_mock.cpp
0 голосов
/ 26 мая 2017

Почему статическая функция?Я бы предложил не делать его статичным.

Затем можно создать интерфейс для класса A (в C ++ это означает класс только с чисто виртуальными заголовками функций) с именем AInterface.Класс A будет реализовывать (наследовать) AInterface и реализовывать эту виртуальную функцию.

Затем передайте указатель на этот интерфейс в конструктор класса B и сохраните его в переменной-члене с именем m_A.Затем в своем тесте создайте MockClassA, который реализует AInterface.Передайте MockClassA в конструктор класса B и установите m_A на вход.

class AInterface
{
   virtual String getSomething() = 0;
}

class A : public AInterface
{
    String getSometing() {
        return String("Something: ") + heavyCalculation().asString();
    }
}

class B 
{
    B(AInterface A) :  { m_A = A; }
    ~B() {}
    String runSomething(const String& name) {
        if(name.equals("something")) {
            return m_A.getSomething();
        } else {
            return "Invalid name!";
        }
    }
    AInterface m_A;
}

Тестовый код

class MockClassA : public AInterface
{
    String getSometing() {
        return String("Whatever I want. This is a test");
    }
}   

void test ()
{
   // "MockClassA" would just be "A" in regular code
   auto instanceOfB = B(MockClassA());

   String returnValue = instanceOfB.runSomething("something");
   :
   :
}
0 голосов
/ 02 ноября 2010

Вы можете передать указатель на функцию конструктору класса A. Затем для тестирования вы можете передать указатель на фиктивную функцию, где вы можете делать все, что захотите.

0 голосов
/ 02 ноября 2010

Вы должны взять класс через шаблон и явно экспортировать этот экземпляр (B<A>), чтобы избежать проблем с компоновщиком, если он ранее не был встроенным, как опубликовано. Таким образом, вы можете вставлять другие классы для целей тестирования по мере необходимости, и в любом случае это хорошая практика. Мне также любопытно, почему ваш пример так похож на Java - мне пришлось прочитать его около пяти раз, прежде чем определить, что это было C ++, как указано.

template<typename T> class BImpl {
    String runSomething(const String& name) {
        if(name.equals("something")) {
            return T::getSomething();
        } else {
            return "Invalid name!";
        }
    }
};
typedef BImpl<A> B; // Just plugs in to existing code.

Теперь вы можете заменить фиктивный класс на А, даже если вы не можете наследовать от него. Infact, это также расширяется другим способом - CRTP.

class A : public BImpl<A> {
    String getSomething() {
        // Now it's non-static! IT'S A MIRACLE!
    }
}

Чудеса шаблонов никогда не перестают меня удивлять.

0 голосов
/ 02 ноября 2010

Я бы сказал: «Чувак, некоторые люди зашли слишком далеко!»

Просто протестируйте два класса как единое целое. Класс А в любом случае жестко закодирован в класс В.

...