Лучший простой способ издеваться над статической / глобальной функцией? - PullRequest
9 голосов
/ 01 июля 2011

У меня есть простой, почти ценностный класс, как Person:

class Person
{
public:
    Person(ThirdPartyClass *object);
    virtual ~Person(void);

    virtual std::string GetFullName() const;
    virtual int GetAge() const;
    virtual int GetNumberOfDaysTillBirthday() const;
};

Я использую стороннюю библиотеку, и ThirdPartyClass должна иметь глобальную / статическую функцию с именем Destroy (часть сторонней библиотеки), вызываемую для ее уничтожения. Эта Destroy функция вызывается в деструкторе Person.

Теперь я пытаюсь выполнить юнит-тест моего класса Person, и мне нужен способ смоделировать / заглушить метод Destroy. Я думаю, что мог бы написать класс-оболочку вокруг статической функции Destroy, а затем использовать инъекцию зависимостей, чтобы внедрить эту оболочку в класс Person, но это выглядит излишним, просто чтобы вызвать эту единственную функцию в этом простом классе. Какой простой и простой способ сделать это? Или внедрение зависимости действительно лучший способ сделать это?

Обновление

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

Я создаю один экземпляр этого класса-обертки в коде моего основного приложения и внедряю его в случае необходимости. Я выбрал этот путь, потому что думаю, что он понятнее. Мне нравится решение Billy ONeal, и я думаю, что оно отвечает на мой вопрос, но я понял, что если я оставлю код на несколько месяцев и вернусь, мне понадобится больше времени, чтобы понять, что происходит по сравнению с внедрением зависимостей. Мне напомнили о дзен-афоризме «явное лучше, чем неявное». и я чувствую, что внедрение зависимостей делает происходящее немного более явным.

Ответы [ 4 ]

9 голосов
/ 01 июля 2011

Создать шов ссылки. Поместите объявление об уничтожении в заголовок и получите два файла реализации:

// Destroy.cpp
void Destroy()
{
    //Code that really does destruction
}

И для тестирования:

// DestroyFake.cpp
void Destroy()
{
    //Code that does fake destruction
}

Затем свяжите первый файл с вашим основным двоичным файлом, а второй файл - с тестовым двоичным файлом.

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

5 голосов
/ 25 марта 2015

Простой, используйте Typemock Isolator ++ (отказ от ответственности - я работаю там)

Добавьте 1 строку в вашем тесте, чтобы подделать / смоделировать глобальное использование Destroy

FAKE_GLOBAL(Destroy);

публичное использование статики:

WHEN_CALLED(Third_Party::Destroy()).Ignore();

частное использование статики:

PRIVATE_WHEN_CALLED(Third_Party::Destroy).Ignore();

Нет необходимости в дополнительном коде / Нет условного кода / Нет разных ссылок.

0 голосов
/ 01 июля 2011

Указатель на функцию создаст способ заменить другую реализацию.Кроме того, он прозрачен для звонящих (если они не делают что-то действительно глупое, например, вызов sizeof(funcname)).

0 голосов
/ 01 июля 2011

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

#include <iostream>

namespace Third_Party_Lib
{
    struct X { };
    void destroy(X*) { std::cout << "Third_Party_Lib::destroy()\n"; }
}

template <typename T>
struct Wrap
{
    Wrap(const T& t) : t_(t) { }
    operator T&() { return t_; }
    operator const T&() const { return t_; }
    T t_;
};

namespace Mine
{

#if TEST_MODE
    // this destroy will be called because it's a better match
    // not needing Wrap::operator T&...
    void destroy(Wrap<Third_Party_Lib::X*>) { std::cout << "Mine::destroy()\n"; }
#endif

    struct Q
    {
        Q(Third_Party_Lib::X* p) : p_(p) { }
        ~Q() { destroy(Wrap<Third_Party_Lib::X*>(p_)); }
        Third_Party_Lib::X* p_;
    };
}

#if TEST_MODE    
int main()
{
    Mine::Q q(new Third_Party_Lib::X);
}
#endif
...