
如何用 GoogleMock 模拟非虚方法实现高性能依赖注入【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest如果你的类没有虚函数又想用 GoogleTest 的 gMock 给依赖注入一个假实现会遇到一个障碍gMock 要求被 mock 的方法必须是虚函数否则调用 mock 对象上的方法时实际执行的是真实对象的方法见 gMock FAQIn order for a method to be mocked, it must bevirtual, unless you use the high-perf dependency injection technique。标准做法是引入接口加虚函数但 Cookbook 明确指出把非虚函数改成虚函数是一个重大决定——它会创建扩展点、削弱对类的控制同时基于接口的方案要付出虚函数调用的开销。当 profiling 确认你确实关心虚函数开销时文档给出的方案是mock non-virtual methods配方gMock 可以把非虚函数用于 Hi-perf dependency injection。这篇文章按 gmock_cook_book.md 中 Mocking Non-virtual Methods 一节的真实步骤演示从定义 mock 类、模板化调用方、设置期望到用 CMake 构建运行测试的完整路径。理解这个技术的关键约束选择发生在编译期因为方法不是虚函数、mock 类与真实类互不继承你无法在运行时决定用真实类还是 mock 类。文档原文you must specify your choice atcompile time(as opposed to run time)。实现方式是把你使用被测依赖的代码模板化生产代码和测试代码分别用不同的类型参数实例化同一个模板。第一步编写与真实类无关的 mock 类mock 类不继承真实类只定义签名相同的方法。语法与 mock 虚函数完全一致唯一区别是不加override来自 Cookbook 的示例// A simple packet stream class. None of its members is virtual. class ConcretePacketStream { public: void AppendPacket(Packet* new_packet); const Packet* GetPacket(size_t packet_number) const; size_t NumberOfPackets() const; ... }; // A mock packet stream class. It inherits from no other, but defines // GetPacket() and NumberOfPackets(). class MockPacketStream { public: MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const)); MOCK_METHOD(size_t, NumberOfPackets, (), (const)); ... };两个要点mock 类不需要定义真实类的所有方法。上例中MockPacketStream没有定义AppendPacket()文档说明这没问题——as long as the test doesnt need to call it。const 方法在第 4 个参数里写(const)MOCK_METHOD宏会自动生成方法定义你不需要另行实现。第二步把使用方模板化在编译期选择类型给使用 packet stream 的代码一个模板类型参数Cookbook 示例template class PacketStream void CreateConnection(PacketStream* stream) { ... } template class PacketStream class PacketReader { public: void ReadPackets(PacketStream* stream, size_t packet_num); };生产代码用真实类实例化CreateConnectionConcretePacketStream()、PacketReaderConcretePacketStream测试代码用 mock 实例化CreateConnectionMockPacketStream()、PacketReaderMockPacketStream。这就是高性能依赖注入的落点——生产路径走非虚的直接调用测试路径换入 mock两边都不为虚调用付开销。第三步在测试中设置期望并驱动被测代码Cookbook 给出的测试用法骨架MockPacketStream mock_stream; EXPECT_CALL(mock_stream, ...)...; .. set more expectations on mock_stream ... PacketReaderMockPacketStream reader(mock_stream); ... exercise reader ...省略号部分遵循 gMock 通用的EXPECT_CALL语法见 gmock_for_dummies.mdEXPECT_CALL(mock_object, method(matchers)) .Times(cardinality) .WillOnce(action) .WillRepeatedly(action);参数位置可以写匹配器不想约束某个参数时写_表示 anything goesusing ::testing::_; using ::testing::AtLeast; EXPECT_CALL(mock_stream, GetPacket(_)).Times(AtLeast(1));Times(5)配合WillOnce(Return(100))这类 action 的组合也是文档中的标准写法示例值取自 for-dummies 的GetX()说明用于规定方法被调用几次、每次返回什么。两条必须遵守的规则期望必须先于调用设置。for-dummies 明确警告gMock requires expectations to be setbeforethe mock functions are called, otherwise the behavior isundefined。不要在EXPECT_CALL与被调 mock 方法之间交替也不要在 mock 已传给被测代码之后再补设期望。析构时自动校验。When a mock is destructed, gMock will automatically check whether all expectations on it have been satisfied即测试结束时未满足的期望会自动使测试失败。用 CMake 构建并运行测试前置条件按 quickstart-cmake.md兼容的操作系统Linux、macOS、Windows、支持 C17 的 C 编译器、CMake 及构建工具。在项目目录创建CMakeLists.txtcmake_minimum_required(VERSION 3.14) project(my_project) # GoogleTest requires at least C17 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip ) # For Windows: Prevent overriding the parent projects compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL FORCE) FetchContent_MakeAvailable(googletest) enable_testing() add_executable( reader_test reader_test.cc ) # quickstart 链接 GTest::gtest_mainREADME 说明 gMock 库目标为 GTest::gmock target_link_libraries( reader_test GTest::gmock GTest::gtest_main ) include(GoogleTest) gtest_discover_tests(reader_test)其中 URL 里的 commit hash 是文档推荐的版本固定方式文档建议经常更新它以指向新版本。然后按 quickstart 的命令构建运行cmake -S . -B build cmake --build build cd build ctestquickstart 展示的ctest成功输出文档示例其中HelloTest是该文档自己的测试Start 1: HelloTest.BasicAssertions 1/1 Test #1: HelloTest.BasicAssertions ........ Passed 0.00 sec 100% tests passed, 0 tests failed out of 1期望没被满足时for-dummies 给出的失败输出长这样文档示例path/to/my_test.cc:119: Failure Actual function call count doesnt match this expectation: Actually: never called; Expected: called at least once. Stack trace: ...看到 Passed 或测试二进制退出成功说明模板实例化、mock 注入和期望校验都按预期工作看到上述 call count 失败信息说明被测代码没有按期望调用 mock 方法。限制与已知边界只能编译期切换mock 类与真实类无继承关系且方法非虚不能在运行时替换这是该技术的前提而非缺陷。可变参数函数无法直接 mockFAQmock 对象无法知道变长参数的个数和类型需要作者提供重载版本文档建议 C 中尽量避免省略号参数。自由函数和静态方法不能直接 mockCookbook Mocking Free Functions需要重写为接口或改用std::function配合 MockFunction。堆上分配的 mock 对象若不释放最终校验不会执行for-dummies Tip 2。链接gtest_main时会默认启用 heap checker如自行管理生命周期注意在测试中删除 mock 对象。若你正被虚函数调用开销困扰文档建议先做 profiling 确认再采用本配方Cookbook Mocking Free Functions 一节。完整示例与更多技巧见 docs/gmock_cook_book.md 的 Mocking Non-virtual Methods 章节期望语法细节见 docs/gmock_for_dummies.md 的 Setting Expectations 章节。【免费下载链接】googletestGoogleTest - Google Testing and Mocking Framework项目地址: https://gitcode.com/GitHub_Trending/go/googletest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考