测试依赖含模板方法类的被测类的惯用模式探讨
测试依赖含模板方法的公共API类的惯用方案
你提到的这个场景确实很常见——当依赖的公共API包含模板方法时,传统的动态多态Mock(依赖虚函数)就不好直接用了,因为模板方法没法直接声明为虚函数。下面是几种业界常用的解决方案:
1. 提取模板方法核心逻辑到非模板虚接口
这是最通用的思路:如果能修改公共API,就把模板方法里类型无关的核心逻辑抽离到基类的虚函数中,让原模板方法只负责类型相关的处理并调用这个虚函数。这样就能用传统的Mock方式控制核心逻辑了。
示例代码:
// 改造后的公共API struct InterfaceBase { virtual void do_core_stuff(const std::type_info& type) = 0; virtual ~InterfaceBase() = default; }; struct Interface : public InterfaceBase { template<class T> void foo() { // 模板层只处理类型相关逻辑,核心逻辑交给虚函数 do_core_stuff(typeid(T)); // 其他类型专属操作... } }; // Mock类 struct InterfaceMock : public InterfaceBase { void do_core_stuff(const std::type_info& type) override { // 按类型定制Mock行为,比如记录调用次数、返回预设结果 if (type == typeid(int)) { int_call_count++; } else if (type == typeid(std::string)) { string_call_count++; } } // 用于验证的计数器 int int_call_count = 0; int string_call_count = 0; }; // 测试代码 void test_tested_class() { InterfaceMock mock; Interface api{mock}; TestedClass tested(api); tested.run_operations(); assert(mock.int_call_count == 2); assert(mock.string_call_count == 1); }
如果无法修改原公共API,可以加一层适配类:自己封装一个带虚接口的适配层,把原API的模板方法调用转换成对虚函数的调用,再Mock这个适配层。
2. 用模板参数注入实现编译期Mock
如果被测类可以改成模板类(或者本身就是模板类),可以把依赖的Interface作为模板参数传入,测试时用自定义的Mock模板类替代原API。
示例代码:
// 原公共API(不可修改) struct Interface { template<class T> void foo() { // 原业务逻辑 } }; // 被测类改成模板类,依赖抽象的API类型 template<typename ApiType> class TestedClass { public: TestedClass(ApiType& api) : api_(api) {} void process_data() { api_.foo<int>(); api_.foo<std::string>(); } private: ApiType& api_; }; // 测试用的Mock模板类 struct InterfaceMock { template<class T> void foo() { // 针对不同类型的Mock逻辑,比如记录调用次数 static int call_count = 0; call_count++; } // 提供验证方法 template<class T> int get_call_count() { // 可以用std::map<std::type_index, int>来存储不同类型的调用次数 static int count = 0; return count; } }; // 测试代码 void test_tested_class() { InterfaceMock mock; TestedClass<InterfaceMock> tested(mock); tested.process_data(); assert(mock.get_call_count<int>() == 1); }
这种方法不需要修改原API,属于编译期多态的思路,适合被测类本身具备模板扩展性的场景。
3. 基于策略模式适配有限类型
如果被测类只用到模板方法的有限几种类型,可以把模板方法的调用转换成对具体类型方法的调用,用策略类封装行为,再Mock这个策略类。
示例代码:
// 定义策略接口(针对被测类用到的类型) struct FooStrategy { virtual void handle_int() = 0; virtual void handle_string() = 0; virtual ~FooStrategy() = default; }; // 适配原API的策略实现 struct RealFooStrategy : public FooStrategy { RealFooStrategy(Interface& api) : api_(api) {} void handle_int() override { api_.foo<int>(); } void handle_string() override { api_.foo<std::string>(); } private: Interface& api_; }; // Mock策略类 struct MockFooStrategy : public FooStrategy { void handle_int() override { int_call_count++; } void handle_string() override { string_call_count++; } // 验证用的计数器 int int_call_count = 0; int string_call_count = 0; }; // 被测类依赖策略接口 class TestedClass { public: TestedClass(FooStrategy& strategy) : strategy_(strategy) {} void run_tasks() { strategy_.handle_int(); strategy_.handle_string(); strategy_.handle_int(); } private: FooStrategy& strategy_; }; // 测试代码 void test_tested_class() { MockFooStrategy mock_strategy; TestedClass tested(mock_strategy); tested.run_tasks(); assert(mock_strategy.int_call_count == 2); assert(mock_strategy.string_call_count == 1); }
这种方法适合被测类使用模板方法的类型固定的场景,把模板调用转化为静态的方法调用,方便Mock和验证。
内容的提问来源于stack exchange,提问作者bartop
相关产品推荐
相关产品推荐

