【问题标题】:Mock static method from external Class (that I can't change!)来自外部类的模拟静态方法(我无法更改!)
【发布时间】:2020-08-28 08:31:43
【问题描述】:

我想模拟(使用 gmock)来自我无法更改的类的静态函数。 A 是我要模拟的类:

Class A
{
public:
   static std::string get_id();
...
}

B 是我想用 gmock 测试的班级:

Class B
{
public:
   B(A *a_ptr);
   ...

   std::string foo();
private:
   A *m_a_ptr;
}

B::B(A *a_ptr) : m_a_ptr(a_ptr)
{
}

std::string B::foo()
{
   id = m_a_ptr->get_id();
   return id;
}

如何在不更改 A 类的情况下模拟方法 get_id?

【问题讨论】:

  • 如果函数是静态的,为什么要用指向A的指针来调用呢?如果您更改静态调用,模拟它会更容易还是我错了?还是你也不能换B?
  • 所以我只使用 A::get_id()?然后我嘲笑 foo() 什么时候我必须在课堂的其他部分使用它?这实际上会起作用,因为 foo 所做的唯一想法是调用 get_id()。谢谢!
  • 听起来很糟糕,但我不熟悉在这种情况下如何正确模拟,但我猜是的。 B::foo 应该叫 B::GetIDOfA 什么的,这可以被嘲笑
  • @RoQuOTriX 您可以通过委托在 GMock 中模拟静态方法,这与在使用要模拟的类型的(类模板化)类型中静态注入生产意图类型与模拟类型一起工作得很好(有关详细信息,请参阅下面的答案)。

标签: c++ unit-testing googlemock


【解决方案1】:

静态依赖注入和 GMock 委托

我们将首先将您的示例最小化为以下内容(以使后面的段落尽可能不嘈杂):

// a.h
#include <string>

// class to mock
class A {
    static std::string get_id();
};

// b.h
#include <string>
#include "a.h"

// class that use A
struct B {
    std::string foo() const {
        return A::get_id();
    }
};

虽然您不能更改A,但您可以在产品代码中将B 更改为静态注入 A,而您可以静态注入A 的模拟委托用于测试代码:

// b.h
#include <string>
#include "a.h"

namespace detail {
// The type template parameter is set to A by default, 
// and should not need to override this default type 
// in production code, but can be injected with 
// mocked classes in test code.
template<typename AImpl = ::A>
struct BImpl {
   std::string foo() const {
        return A::get_id();
   }
};
}  // namespace detail

// Expose product-intent specialization.
using B = BImpl<>;

A 的模拟使用静态(非线程安全)方法来模拟对注入的静态类型的调用:

// a_mock.h
#include <memory>
#include <string>
#include "gmock/gmock.h"

class AMock {
    // Mocked methods.
    struct Mock {
        MOCK_CONST_METHOD0(get_id,
                           std::string());
    };

    // Stubbed public API for static function of object under test:
    // delegates stubbed calls to the mock.
    static std::string get_id() {
        if (const auto mock = mock_.lock()) {
            mock->get_id();
        }
        else {
            ADD_FAILURE() 
                << "Invalid mock object! The test can no "
                   "longer be considered useful!";   
        }
    }

    // Public setter to specify the mock instance used in test (which in
    // turn will be the instance that Google Test's EXPECTS and mocked
    // calls is placed upon).
    static void setMock(const std::shared_ptr<Mock>& mock) { mock_ = mock; }

  private:
    // Pointer to mock instance.
    static std::weak_ptr<Mock> mock_;
};

最后可以在BImpl的测试中使用如下:

// b_test.cpp
#include "b.h"  // object under test
#include "gmock/gmock.h"
#include "a_mock.h"

class BImplTest : public ::testing::Test {
public:
    using BImplUnderTest = BImpl<AMock>;

    BImplTest() : amock_(std::make_shared<AMock::Mock>()) {
        AMock::setMock(amock_);
    }
};

TEST_F(BImplTest, foo) {
    // Setup mocked call(s).
    EXPECT_CALL(amock_, foo()).WillOnce(::testing::Return( /*...*/ ));

    // Call object under test.
    BImplUnderTest b{};
    b.foo();
      
}

进一步隐藏B实际上是类模板BImpl的特化这一事实

如果你开始大量使用这种模式(在不同的子例程上以滑动窗口的方式)并且想要避免单个大而臃肿的翻译单元,你可以移动 detail::B 类的成员函数的定义用于分隔标头的模板,例如 b-timpl.h(包括 b.h)和与 b.h 关联的源文件中,例如 b.cpp,包括 b-timpl.h 而不是 b.h,并为生产添加显式实例化定义意图detail::BImpl 专业化:

// b.cpp
template class ::detail::BImpl<>;

而在::detail::BImpl 的测试中,您包括b-timpl.h 而不是b.h,并为类模板的模拟注入专业化添加显式实例化定义:

// b_test.cpp
#include "b-timpl.h"
// ...

template class ::detail::BImpl<AMock>;

// ...

为什么? BImpl 类未参数化以允许其接口的用户静态注入不同的行为(对于用户意图,用户应该只看到 B),但允许在测试时注入模拟或存根类。

【讨论】:

  • 谢谢,但是这样代码真的很复杂,还是?
  • @dfrib 我认为您的BImpl 实现可能有错误。不应该是return AImpl::get_id(); 而不是return A::get_id();
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多