单元测试Mock socket全局函数失败:预期调用未触发
解决方案:不修改生产代码Mock全局Socket函数
针对你遇到的问题,核心是通过链接器符号覆盖或预编译宏替换,在不改动生产代码的前提下替换系统原生的socket()/send()/recv()/close()函数,结合GMock完成单元测试。以下是具体实现方案:
方法1:链接器符号覆盖(推荐,适配所有场景)
C语言全局函数默认是弱符号,测试代码中定义同名函数时,链接器会优先使用测试中的实现,自动覆盖系统库的原生函数。我们可以把这些函数转发到GMock对象上。
步骤1:定义Socket Mock类
#include <gmock/gmock.h> #include <sys/socket.h> class SocketMock { public: MOCK_METHOD(int, socket, (int domain, int type, int protocol), ()); MOCK_METHOD(ssize_t, send, (int sockfd, const void *buf, size_t len, int flags), ()); MOCK_METHOD(ssize_t, recv, (int sockfd, void *buf, size_t len, int flags), ()); MOCK_METHOD(int, close, (int fd), ()); }; // 全局指针,用于转发系统函数调用到Mock对象 extern SocketMock* g_socket_mock;
步骤2:实现全局转发函数
必须用extern "C"包裹,保证函数名遵循C语言链接约定,避免C++编译器修改函数名导致无法覆盖:
#include "socket_mock.h" SocketMock* g_socket_mock = nullptr; extern "C" { int socket(int domain, int type, int protocol) { return g_socket_mock->socket(domain, type, protocol); } ssize_t send(int sockfd, const void *buf, size_t len, int flags) { return g_socket_mock->send(sockfd, buf, len, flags); } ssize_t recv(int sockfd, void *buf, size_t len, int flags) { return g_socket_mock->recv(sockfd, buf, len, flags); } int close(int fd) { return g_socket_mock->close(fd); } }
步骤3:编写测试用例
#include "client_connection.h" #include "socket_mock.h" #include <gmock/gmock.h> TEST(ClientConnectionTest, ConstructorCreatesSocket) { SocketMock mock; g_socket_mock = &mock; // 设置预期:调用AF_INET域的流式socket,返回模拟的文件描述符123 EXPECT_CALL(mock, socket(AF_INET, SOCK_STREAM, 0)) .WillOnce(testing::Return(123)); // 构造ClientConnection,此时会调用Mock的socket函数 ClientConnection conn; // 重置全局指针,避免污染其他测试用例 g_socket_mock = nullptr; } TEST(ClientConnectionTest, SendDataCallsSendFunction) { SocketMock mock; g_socket_mock = &mock; EXPECT_CALL(mock, socket(AF_INET, SOCK_STREAM, 0)) .WillOnce(testing::Return(456)); EXPECT_CALL(mock, send(456, testing::_, 5, 0)) .WillOnce(testing::Return(5)); ClientConnection conn; conn.send_data("hello", 5); g_socket_mock = nullptr; }
注意事项
- 确保测试代码的链接顺序:链接器会优先使用当前二进制中的符号,因此只要测试代码被正确链接,就能覆盖系统库的函数。
- 若生产代码中使用了这些函数的函数指针,该方法依然有效(因为指针指向的是我们的转发函数)。
方法2:预编译宏替换(适合简单场景)
如果不需要复杂的Mock验证,仅需替换函数行为,可以用宏直接替换系统函数。但该方法无法处理函数指针场景。
在测试文件的最开头(必须在包含<sys/socket.h>或生产代码头文件之前)定义宏:
// 宏替换系统函数为Mock对象的方法 #define socket(domain, type, protocol) g_socket_mock->socket(domain, type, protocol) #define send(sockfd, buf, len, flags) g_socket_mock->send(sockfd, buf, len, flags) #define recv(sockfd, buf, len, flags) g_socket_mock->recv(sockfd, buf, len, flags) #define close(fd) g_socket_mock->close(fd) // 后续包含生产代码头文件和Mock定义 #include "client_connection.h" #include "socket_mock.h" #include <gmock/gmock.h> // 测试用例编写同方法1
常见问题排查
- Mock方法未被触发:检查是否用
extern "C"包裹了转发函数,C++编译时必须保证函数名不被mangle。 - 链接错误:确认测试代码中没有重复定义这些函数,且全局Mock指针已正确初始化。
内容的提问来源于stack exchange,提问作者headfxll
相关产品推荐
相关产品推荐

