Насколько ужасен класс Static-Casting Base для класса Derived для вызова деструктора в тестах (для тестов)? - PullRequest
0 голосов
/ 25 февраля 2019

Предисловие: Я знаю, что следующее - очень грязный хак.Мы будем рефакторировать вещи (чтобы мы могли писать нехакерские тесты, использовать Mocks и, возможно, даже избавляться от синглетонов), но сначала нам нужно тестовое покрытие ...

Как уже было сказано, у нас есть значительное числоклассов типа «Синглтон» в нашем проекте, то есть что-то вроде.

class SomeSingleton
{
  public:
    static SomeSingleton& getSingleton()
    { 
        if (m_singletonInstance == nullptr)
            m_singletonInstance = new SomeSingleton();
        return *m_singletonInstance;
    }

  protected:
    SomeSingleton();
    virtual ~SomeSingleton();

    static SomeSingleton* m_singletonInstance = nullptr;
}

Чтобы правильно запустить наши модульные тесты, синглтон должен быть «сброшен» (или данные наших предыдущих тестов могутсохраняться в синглтоне, влияя на текущий тест).К сожалению, нет подходящего способа сделать это (экземпляр Singleton и деструктор защищены).

Мы не хотим изменять наш производственный код так, чтобы он содержал функциональность только для тестирования (то есть без прямого объявления ::test::SomeSingletonResetterкак друг в SomeSingleton, не вводите общедоступную функцию void resetSingleton_forTestUseOnly() и т. д.), и мы также хотим избежать каких-либо существенных изменений в производственном коде, пока у нас не будет тестового покрытия.

Итак, вот грязнаяХак, который мы рассматриваем: В тестовом проекте мы извлекаем очень простой класс-обертку (то есть без членов) из Singleton со статической функцией, которая delete s синглтоном - однако, поскольку деструктор не может быть вызван даже из производного класса (онзащищен) мы бы static_cast синглтона для производного класса и удалили это:

class SomeSingletonWrapper : public SomeSingleton
{
  public:
    SomeSingletonWrapper();
    ~SomeSingletonWrapper() override;

    static void reset()
    {
        //can't delete/call destructor on SomeSingleton since it is protected
        delete static_cast<SomeSingletonWrapper*>(m_singletonInstance);
        m_singletonInstance = nullptr;
    }
}

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

Редактировать:

Я знаю, что альтернатива будет иметьЯ просто не вызывал деструктор (и только устанавливал m_singletonInstance = nullptr), но это было бы еще хуже, так как теперь я теряю память при каждом тесте, и эти синглтоны продолжают плавать, пока тестовое приложение не завершится, выполняя "бог знает что" ..Бррр ....

1 Ответ

0 голосов
/ 25 февраля 2019

Согласно стандарту 5.3.5.2:

В первом варианте ( удалить объект ) значение операнда удаления может быть значением нулевого указателя, aуказатель на объект, не являющийся массивом, созданный предыдущим новым выражением, или указатель на подобъект (1.8), представляющий базовый класс такого объекта (раздел 10). Если нет, поведение не определено .

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

Можете ли вы переопределить getSingleton()?Это не virtual или static в данном коде, но, вероятно, это должен быть один из них.Если первое, есть более простое решение (переопределите его в вашей обертке).Если нет, вы можете попытаться заменить созданное значение своей оболочкой:

class SomeSingletonWrapper : public SomeSingleton
{
public:
    static void injectWrapper()
    {
        // Should be null or else someone used it before we replaced it
        if( m_singletonInstance != nullptr ) 
        {
            throw std::exception( "Singleton wrapping failed!" );
        }
        m_singletonInstance = new SomeSingletonWrapper();
    }

    static void resetWrapper()
    {
        auto wrapper = dynamic_cast<SomeSingletonWrapper*>( m_singletonInstance );
        if( wrapper == nullptr )
        {
            throw std::exception( "Singleton wrapping failed in the reset!" );
        }

        delete wrapper;
        m_singletonInstance = nullptr;
    }
}

Затем в своем тесте замените его, прежде чем кто-либо его использует (но остерегайтесь фиаско инициализации статического порядка *)где что-то еще может получить старый экземпляр первым):

class MyTest
{
public:
    void testSetup()
    {
        SomeSingletonWrapper::injectWrapper();    
    }

    void testCleanup()
    {
        SomeSingletonWrapper::resetWrapper();
    }

    void testMySingletonUse()
    {
        auto& single = SomeSingleton::getInstance();
        ASSERT( dynamic_cast<SomeSingletonWrapper*>( &single ) ); // Yay! It worked!
        // More testy stuff using 'single'
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...