如何用GTest测试智能指针指向的类方法跨类调用
我有如下类定义:
class MinorClass{ virtual void NestedFunction(){...}; };
该类被另一个类使用:
class MajorClass{ public: MajorClass(unique_ptr<MinorClass>&& input) : minor_class{std::move(input)}{}; ... std::unique_ptr<MinorClass> minor_class; void foo(){minor_class->NestedFunction();}; }
我想用GTest编写测试,验证foo()方法是否调用了NestedFunction(),于是实现了GMock的Mock类:
class MockMinorClass : public MinorClass { public: MOCK_METHOD(void, NestedFunction, (), (override)); };
并写了测试用例:
TEST(MajorClassTest, TestingNestedCalls) { // given auto minor_class = std::make_unique<MinorClass>(new MockMinorClass()); auto major_class = MajorClass(std::move(minor_class)); // then EXPECT_CALL(*(major_class.minor_class), NestedFunction()).Times(1); major_class.Init(); }
但报错:MinorClass has no member named 'gmock_NestedFunction。另外我补充了最小可复现示例,测试会失败(实际未调用目标函数),还注意到必须给MinorClass`加虚析构函数,否则会出现Mock对象内存泄漏错误:
ERROR: this mock object (used in test
MajorClassTest.TestingNestedCalls) should be deleted but never is.
补充的可复现代码:
#include <gtest/gtest.h> #include <gmock/gmock.h> class MinorClass{ public: virtual void NestedFunction(){ std::cerr<<"Invoked MinorClass::NestedFunction()\n"; }; virtual ~MinorClass()=default; }; class MajorClass{ public: MajorClass(std::unique_ptr<MinorClass>&& input) : minor_class{std::move(input)}{}; std::unique_ptr<MinorClass> minor_class; void foo(){ std::cerr<<"Invoked MajorClass::foo()\n"; minor_class->NestedFunction(); }; }; class MockMinorClass : public MinorClass { public: MOCK_METHOD(void, NestedFunction, (), (override)); }; TEST(MajorClassTest, TestingNestedCalls) { auto minor_class = std::make_unique<MockMinorClass>(); // Keep the reference to the instance of `MockMinorClass`. auto* mock_minor = minor_class.get(); auto major_class = MajorClass(std::move(minor_class)); EXPECT_CALL(*mock_minor, NestedFunction()); }
请问如何测试这种通过智能指针指向的类实例被另一个类调用方法的场景?
1. 修复对象切片问题
最初的错误源于对象切片:std::make_unique<MinorClass>(new MockMinorClass())创建的是MinorClass类型对象,而非MockMinorClass。虽然Mock类继承自基类,但这里会截断Mock对象的特有部分,导致GMock的方法无法被识别。
正确做法是直接创建MockMinorClass的智能指针:
auto minor_class = std::make_unique<MockMinorClass>();
2. 触发目标方法调用
补充测试用例的失败核心是未调用major_class.foo()——只设置了调用预期,却没触发实际调用,GMock自然检测不到方法执行。必须先设置预期,再调用目标方法。
3. 保留Mock对象的裸指针
在将unique_ptr的所有权转移给MajorClass前,要先获取Mock对象的裸指针,后续用这个指针设置EXPECT_CALL。因为转移所有权后,原智能指针会失效,无法再访问Mock对象。
4. 确保虚析构函数存在
基类MinorClass必须有虚析构函数,否则销毁unique_ptr<MinorClass>时只会调用基类析构函数,不会触发Mock类的析构逻辑,导致GMock检测到内存泄漏。你的补充代码里已经做了这部分,是正确的。
#include <gtest/gtest.h> #include <gmock/gmock.h> #include <memory> #include <iostream> class MinorClass{ public: virtual void NestedFunction(){ std::cerr<<"Invoked MinorClass::NestedFunction()\n"; }; // 必须有虚析构函数,避免切片导致Mock对象泄漏 virtual ~MinorClass()=default; }; class MajorClass{ public: MajorClass(std::unique_ptr<MinorClass>&& input) : minor_class{std::move(input)}{}; std::unique_ptr<MinorClass> minor_class; void foo(){ std::cerr<<"Invoked MajorClass::foo()\n"; minor_class->NestedFunction(); }; }; class MockMinorClass : public MinorClass { public: MOCK_METHOD(void, NestedFunction, (), (override)); }; TEST(MajorClassTest, TestingNestedCalls) { // 创建Mock对象的unique_ptr auto minor_class = std::make_unique<MockMinorClass>(); // 保存Mock对象的裸指针,后续用于设置调用预期 MockMinorClass* mock_minor = minor_class.get(); // 把所有权转移给MajorClass MajorClass major_class(std::move(minor_class)); // 设置调用预期:期望NestedFunction被调用一次 EXPECT_CALL(*mock_minor, NestedFunction()).Times(1); // 触发目标方法,让Mock检测调用 major_class.foo(); } int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
- 避免对象切片:直接创建Mock类的智能指针,通过多态传递给基类指针。
- 先设置预期再触发调用:GMock需要提前知道预期,才能验证后续的调用行为。
- 提前保留Mock裸指针:在转移智能指针所有权前获取,用于设置
EXPECT_CALL。 - 基类必须有虚析构函数:保证Mock对象能被正确销毁,避免内存泄漏警告。
内容的提问来源于stack exchange,提问作者roschach

