CRTP静态多态:WebSocket服务器派生类测试能否用Mock替换基类?
解决CRTP静态多态下的测试循环引用问题
这个循环引用问题确实是CRTP在单元测试场景下的典型痛点——既要保留生产环境中基类与业务类的静态绑定,又要在测试时替换基类为Mock,直接改模板参数很容易陷入无限递归的陷阱。下面是一种既能保持CRTP性能优势,又能完美支持单元测试的解决方案:
核心思路:拆分业务逻辑与CRTP继承关系
我们将原有的业务类拆分为通用业务逻辑模板和CRTP基类模板,让业务逻辑通过模板参数注入基类;生产环境中通过闭合CRTP循环实现原有的功能,测试环境则直接注入Mock基类即可。
步骤1:定义通用业务逻辑模板
把原来WebsocketSession中的业务逻辑抽成一个模板类,它继承自传入的基类模板参数,这样可以灵活替换基类:
template<typename Base> class WebsocketBusiness : public Base { public: // 业务方法:调用基类的收发逻辑 void someDerivedMethod() { // 直接调用基类方法,CRTP或Mock基类都能兼容 this->otherBaseMethod(); } // 供基类调用的业务处理方法 void otherDerivedMethod() { // 这里写你的具体业务逻辑,比如解析WebSocket消息、处理业务请求等 } };
步骤2:定义CRTP基类模板
保留原有的WebSocket收发逻辑,基类通过CRTP调用业务类的方法:
template<typename Derived> class WebsocketSessionBase { public: // 基类方法:调用派生类的业务处理逻辑 void someBaseMethod() { static_cast<Derived*>(this)->otherDerivedMethod(); } // 实际的WebSocket收发实现 void otherBaseMethod() { // 这里写生产环境的WebSocket发送/接收逻辑,比如调用底层API发送消息 } };
步骤3:生产环境闭合CRTP循环
通过定义一个具体的类来闭合CRTP的递归依赖,这完全符合CRTP的标准用法,编译器可以正常处理这种递归继承:
// 生产环境的最终会话类:业务逻辑模板 + CRTP基类 class ProdWebsocketSession : public WebsocketBusiness<WebsocketSessionBase<ProdWebsocketSession>> {};
步骤4:测试环境注入Mock基类
测试时,我们只需要定义一个Mock基类,然后注入到业务逻辑模板中即可,完全不需要修改业务代码:
#include <gmock/gmock.h> // Mock基类:模拟WebSocket收发逻辑 class MockWebsocketBase { public: // Mock基类方法,供业务类调用 MOCK_METHOD(void, otherBaseMethod, ()); // 如果需要测试基类调用业务方法的逻辑,可以添加这个方法 void someBaseMethod(WebsocketBusiness<MockWebsocketBase>& business) { business.otherDerivedMethod(); } }; // 测试用的会话类型:业务逻辑模板 + Mock基类 using TestWebsocketSession = WebsocketBusiness<MockWebsocketBase>; // 单元测试示例 TEST(WebsocketSessionTest, CallsBaseMethodOnBusinessAction) { TestWebsocketSession session; // 验证业务方法调用了Mock基类的收发方法 EXPECT_CALL(static_cast<MockWebsocketBase&>(session), otherBaseMethod()).Times(1); session.someDerivedMethod(); } TEST(WebsocketSessionTest, ProcessesBusinessLogicWhenBaseCalls) { TestWebsocketSession session; // 模拟基类触发业务逻辑处理,这里可以验证业务逻辑的执行结果 static_cast<MockWebsocketBase&>(session).someBaseMethod(session); // 比如添加断言验证业务逻辑的副作用,比如消息是否被正确解析等 }
为什么这个方案能解决循环问题?
- 生产环境中,
ProdWebsocketSession继承自WebsocketBusiness<WebsocketSessionBase<ProdWebsocketSession>>,而WebsocketSessionBase的模板参数是ProdWebsocketSession本身——这是CRTP的标准用法,编译器允许这种“不完全类型”的递归引用,因为CRTP只需要进行指针转换,不需要完整的类型定义。 - 测试环境中,
TestWebsocketSession直接继承MockWebsocketBase,不存在任何递归依赖,完全独立于生产环境的CRTP基类,实现了业务逻辑与底层收发逻辑的彻底解耦。
内容的提问来源于stack exchange,提问作者Gyorgy Szekely
相关产品推荐
相关产品推荐

