Как передать std :: function в качестве аргумента имитируемому методу в gmock? - PullRequest
1 голос
/ 17 июня 2020

Я получаю сообщение об ошибке выполнения при попытке передать std:function в качестве аргумента имитируемому методу внутри EXPECT_CALL. Я использую sh, чтобы убедиться, что Bar вызывается с callback в качестве аргумента.

Код:

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class Foo {
public:
    virtual void Bar(const std::function<void (const std::string &name)> &callback) = 0;
};

class MockFoo : public Foo {
public:
    MOCK_METHOD(void, Bar, (const std::function<void (const std::string &name)> &callback));
};

TEST(FooBarTest, callback) {
    MockFoo foo;

    const std::function<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(callback)).Times(1);

    foo.Bar(callback);
}

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

Во время выполнения возникает ошибка:

/usr/local/include/gtest/gtest-matchers.h:211:60: error: no match for ‘operator==’ (operand types are ‘const std::function<void(const std::__cxx11::basic_string<char>&)>’ and ‘const std::function<void(const std::__cxx11::basic_string<char>&)>’)
   bool operator()(const A& a, const B& b) const { return a == b; }

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Аргументы метода, указанного в EXPECT_CALL, на самом деле являются сопоставителями. Когда вы просто указываете значение (что-то, что еще не относится к типу gmock Matcher<T>), это подразумевает сопоставление Eq. Итак, EXPECT_CALL(foo, Bar(callback)) на самом деле означает EXPECT_CALL(foo, Bar(Eq(callback))). Проблема в том, что std::function не предоставляет operator== для сравнения двух функций. Его свойства стирания типов означают, что проверку на равенство невозможно реализовать в целом, плюс, конечно, некоторые из типов функторов классов, которые он может обернуть, также не будут иметь своих operator==.

Но это так. можно проверить, содержит ли std::function очень специфический объект c. Если вы не хотите просто игнорировать аргумент, ожидая Bar(_), вот идея для определения того, является ли std::function фиктивной функцией c спецификацией.

Сначала создайте вызываемый класс, который мы будет использовать для инициализации объекта std::function. У меня также будет его operator() вызов фиктивного метода, который не нужен, когда он просто передается в фиктивную функцию, но это позволит использовать один и тот же std::function в разных контекстах или установить ожидания до одного реального функция, которая будет вызывать его напрямую и передавать в имитируемый интерфейс.

template <typename FuncT> class DummyFunction; // undefined

template <typename RetType, typename... ArgTypes>
class DummyFunction<RetType(ArgTypes...)> {
public:
    constexpr DummyFunction() : DummyFunction(0) {}
    explicit constexpr DummyFunction(int key) : m_key(key) {}
    constexpr DummyFunction(const DummyFunction& f) : m_key(f.m_key) {}
    constexpr int key() const { return m_key; }

    MOCK_METHOD(RetType, doCall, (ArgTypes...));

    RetType operator()(ArgTypes... args)
    { return doCall(std::forward<ArgTypes>(args)...); }

    friend constexpr bool operator==(const DummyFunction& f1, const DummyFunction& f2)
    { return f1.m_key == f2.m_key; }
    friend constexpr bool operator!=(const DummyFunction& f1, const DummyFunction& f2)
    { return !(f1 == f2); }
    friend std::ostream& operator<<(std::ostream& os, const DummyFunction& f)
    { return os << "DummyFunction(" << f.m_key << ")"; }

private:
    int m_key;
};

Затем gmock Matcher, чтобы проверить, содержит ли std::function объект DummyFunction с тем же типом функции, что и параметр шаблона и тот же ключ, что и данный объект DummyFunction, может выглядеть так. Поскольку можно преобразовать один тип std::function в другой, если типы параметров и возвращаемые типы преобразуются правильно (или возвращаемый тип изменяется на void), я сделал его сопоставителем "polymorphi c", который принимает любые std::function специализация для тестирования.

template <class DummyFuncType>
class IsDummyFunctionTester {
public:
    explicit constexpr IsDummyFunctionTester(int key) : m_key(key) {}

    // The three member functions required for gmock "PolymorphicMatcher":
    template <typename FuncType>
    bool MatchAndExplain(const std::function<FuncType>& f,
                         ::testing::MatchResultListener* listener) const {
        bool type_ok = f.target_type() == typeid(DummyFuncType);
        if (type_ok) {
            int f_key = f.template target<DummyFuncType>()->key();
            if (f_key == m_key) return true;
            *listener << "std::function contains DummyFunction(" << m_key << ")";
        } else if (!f) {
            *listener << "std::function is empty";
        } else {
            // Note name() is implementation dependent. For g++/clang it's mangled.
            *listener << "std::function target's type_info::name() is "
                      << f.target_type().name();
        }
        return false;
    }

    void DescribeTo(std::ostream* os) const
    { *os << "is a DummyFunction(" << m_key << ")"; }
    void DescribeNegationTo(std::ostream* os) const
    { *os << "is not a DummyFunction(" << m_key << ")"; }

private:
    int m_key;
};
template <typename FuncType>
decltype(auto) StdFuncIsDummyFunc(const DummyFunction<FuncType>& f) {
    return ::testing::MakePolymorphicMatcher(
        IsDummyFunctionTester<DummyFunction<FuncType>>(f.key()));
}

Итак, наконец, вы можете сделать:

TEST(FooBarTest, callback) {
    MockFoo foo;

    const DummyFunction<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(StdFuncIsDummyFunc(callback))).Times(1);

    foo.Bar(callback);
}

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

1 голос
/ 17 июня 2020

Существуют заранее определенные ACTIONS, которые вы можете использовать для этой цели. Ваш EXPECT_CALL будет выглядеть примерно так:

using namespace testing; // for brevity

TEST(FooBarTest, callback) {
    MockFoo foo;

    const std::function<void(const std::string &name)> callback;

    EXPECT_CALL(foo, Bar(_)).WillOnce(Invoke(callback));

    foo.Bar(callback);
}

Как указано @IanGralinski, для std::function нет сопоставителя, поэтому вы можете использовать любой сопоставитель для вызова (например, _).

Однако я бы использовал здесь gmock не так - зачем высмеивать Foo, если вы используете его напрямую? Обычно макеты используются, когда вы тестируете взаимодействия своего (реального) класса с другими классами (макеты). Итак, в вашем примере: Foo может быть высмеян с помощью MockFoo и использоваться каким-либо другим классом (реальным), используя инъекцию зависимостей.

Кстати, не забудьте добавить виртуальный деструктор в Foo, если объект, производный от Foo, должен быть удален указателем на Foo (это будет UB без виртуального dtor).

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