Gtest: насмешливая функция в конструкторе - PullRequest
0 голосов
/ 12 февраля 2019

Я прочитал много документации, связанной с мошенничеством в Gtest (например, https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md, "Mocking Free Functions"), но не смог найти решение для следующей проблемы:

source.cpp

H::H()
{
    // some code1
    if (to_be_mocked(id) != 0) { // some code2 }
    // some code3
}

H& H::get_instance()
{
    static H s;
    return s;
}

unit_test.cpp

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "source.h"

TEST(Source, Constructor)
{
    // What to write here to mock function "to_be_mocked"?
    H& inst = H::get_instance();
}

int main(int argc, char** argv)
{
  testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

Итак, мне нужно протестировать весь код в конструкторе Hи фиктивная функция to_be_mocked, которая определена в другой единице перевода.Как я могу сделать это с unit_test.cpp?

1 Ответ

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

Зависимость впрыска (DI) на помощь!

DI - ключевой инструмент для насмешек.В частности, вы можете использовать шаблон стратегии , чтобы ввести зависимость в этот объект, чтобы вы могли использовать ее во время тестирования.

Вариант 1: Внедрение в конструктор

Простейшей версией этого было бы передать функтор вашему конструктору и вызвать его там, где вы в данный момент вызываете to_be_mocked().

В этом случае ваш класс будет выглядеть примерно так:

class H 
{
    std::function<bool(int)> _to_be_mocked;
public:
    H( std::function<bool(int)> fn ) 
        : _to_be_mocked( std::move(fn) ) 
    {
        uses_mockable( 42 ); 
    }

    void uses_mockable( int id ) 
    {
        if( _to_be_mocked(id) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    auto h = H{ my_mock_fn };

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

DI плохо работает с глобальными / синглетонами, потому что вы не можете (легко) вводить зависимости во время создания, что является одной из причин, почему онине рекомендуется.

Вариант 2: Внедрение свойства

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

В этом случае ваш класс будет выглядеть так:

class H 
{
    std::function<bool(int)> _to_be_mocked;
public:

    H() 
        : _to_be_mocked( to_be_mocked ) // Use stand-alone function for default
    { /* can't use mock here */ }

    // Could restrict accessibility here with Attorney-Client idiom or friendship
    void set_to_be_mocked( std::function<bool(int)> fn ) 
    { 
        _to_be_mocked = std::move( fn ); 
    }

    void uses_mockable( int id ) 
    {
        if( _to_be_mocked( id ) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    auto& h = H::get_instance();
    // ...
    h.set_to_be_mocked( my_mock_fn );

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

Этот подход не будет работать, если вам нужно вызвать _to_be_mocked() в вашем конструкторе, поэтому вам придется перейти на прежний подход (предпочтительно) ИЛИ перейти на Option3.

Вариант 3: DI бедного по глобальной переменной

Если вы не можете использовать ни один из вышеперечисленных подходов, вы можете использовать «DI бедного человека» с помощью Yet Another Global (sinпорождает грех, говорят они).Один из способов сделать это в вашем случае без изменения вызывающего кода - переименовать to_be_mocked() во что-то вроде to_be_mocked_impl() и создать глобальный функтор с именем to_be_mocked:

bool to_be_mocked_impl( int id ) { ... } // used to be called to_be_mocked(int)

// Global instance that can be swapped out for testing.
// Defaults to the normal runtime function.
// Might use a raw function pointer instead of a std::function if you prefer.
auto to_be_mocked = std::function<bool(int)>{ to_be_mocked_impl };

class H 
{
public:
    H() 
    {
        uses_mockable( 42 ); 
    }

    void uses_mockable( int id ) 
    {
        if( to_be_mocked(id) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    to_be_mocked = my_mock_fn; // Sets global ... blah!!

    auto& h = H::get_instance();

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

. Это усложняет ситуацию с глобальными переменными./ singletons, так что я бы не рекомендовал это, если вам не нужно по необоснованным причинам.

PS, был эпизод CppCast на Dependency Injection , в котором говорилось об экспериментальном [Boost.] DI библиотека , которая может быть полезна здесь.

...