You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用GMock模拟QUdpSocket测试ServerUDP类?解决绑定方法未触发问题

问题:使用GMock测试依赖QUdpSocket的ServerUDP类失败

我在ServerUDP类中实现了init方法,用于初始化QUdpSocket,现在想用GMock对该类做单元测试,完全不依赖Qt真实组件。

QAbstractSocket中bind方法声明:

bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);

我创建了Mock类和测试对象:

class QUdpSocketMock : public QUdpSocket
{
public:
//    MOCK_METHOD(bool, bind, (const QHostAddress&, quint16, BindMode));
    MOCK_METHOD3(bind, bool(const QHostAddress&, quint16, BindMode));
};

class ServerUDPTest : public ::testing::Test
{
protected:
    QUdpSocketMock socketMock;
    ServerUDP serverUDP{socketMock};
};

测试用例期望bind返回false,但EXPECT_CALL提示bind从未被调用:

TEST_F(ServerUDPTest, TestCase)
{
    EXPECT_CALL(socketMock, bind).WillOnce(Return(false));
    EXPECT_EQ(Error_Code_T::ERROR, serverUDP.init());
}

ServerUDP类定义:

class ServerUDP : public QObject, public ServerInterface
{
    Q_OBJECT

public:
    explicit ServerUDP(QUdpSocket& socket_, QObject *parent = nullptr);
    virtual ~ServerUDP() {};

    virtual Error_Code_T init();

    virtual Error_Code_T start();

    virtual Error_Code_T stop();

    virtual Error_Code_T deinit();

public slots:
    virtual void readData();

private:
    QUdpSocket& socket;

    QString hostName;
    QString localHostAddress;
    quint16 port;

    ServerUDP() = delete;
    ServerUDP(ServerUDP&) = delete;
    ServerUDP& operator=(ServerUDP&) = delete;
};

问题:为什么bind没被调用?如何不依赖Qt真实组件测试该类?是不是QUdpSocket是QObject导致的?


原因分析与解决方案

核心原因:QUdpSocket::bind不是虚函数

GMock只能mock虚函数或者基于抽象接口的方法。而Qt的QAbstractSocket::bind是普通成员函数,并非虚函数——你继承QUdpSocket创建的mock类中,用MOCK_METHOD3定义的bind并不会被ServerUDP调用,因为ServerUDP里的socket是QUdpSocket&类型,调用socket.bind(...)时会直接触发基类的真实方法,而非mock版本。

解决步骤:抽象套接字接口

要脱离Qt真实组件测试,必须对QUdpSocket做接口抽象,让ServerUDP依赖抽象接口而非具体类。

  1. 定义抽象接口类
    创建包含ServerUDP所需QUdpSocket方法的纯虚接口:
class IUdpSocket : public QObject
{
    Q_OBJECT
public:
    virtual ~IUdpSocket() = default;
    virtual bool bind(const QHostAddress &address, quint16 port = 0, QAbstractSocket::BindMode mode = QAbstractSocket::DefaultForPlatform) = 0;
    // 按需添加ServerUDP用到的其他方法,比如close()、state()等
};
  1. 修改ServerUDP依赖抽象接口
    将ServerUDP中的QUdpSocket&替换为IUdpSocket&:
class ServerUDP : public QObject, public ServerInterface
{
    Q_OBJECT

public:
    // 构造函数参数改为抽象接口引用
    explicit ServerUDP(IUdpSocket& socket_, QObject *parent = nullptr);
    virtual ~ServerUDP() {};

    virtual Error_Code_T init();

    // 其他方法保持不变...

private:
    IUdpSocket& socket; // 替换为抽象接口引用
    // 其他成员变量不变...
};
  1. 创建Mock类实现抽象接口
    用GMock实现IUdpSocket接口:
class UdpSocketMock : public IUdpSocket
{
    Q_OBJECT
public:
    MOCK_METHOD3(bind, bool(const QHostAddress&, quint16, QAbstractSocket::BindMode));
    // 若ServerUDP用到其他方法,在此添加对应的MOCK_METHOD
};
  1. 更新测试用例
    使用新的Mock类编写测试:
class ServerUDPTest : public ::testing::Test
{
protected:
    UdpSocketMock socketMock;
    ServerUDP serverUDP{socketMock};
};

TEST_F(ServerUDPTest, TestCase)
{
    // 用_匹配任意参数,也可指定具体值
    EXPECT_CALL(socketMock, bind(_, _, _)).WillOnce(Return(false));
    EXPECT_EQ(Error_Code_T::ERROR, serverUDP.init());
}
  1. 适配生产环境
    在真实业务代码中,创建包装真实QUdpSocket的实现类:
class RealUdpSocket : public IUdpSocket
{
    Q_OBJECT
public:
    RealUdpSocket(QObject* parent = nullptr) : udpSocket(parent) {}
    bool bind(const QHostAddress &address, quint16 port = 0, QAbstractSocket::BindMode mode = QAbstractSocket::DefaultForPlatform) override
    {
        return udpSocket.bind(address, port, mode);
    }
private:
    QUdpSocket udpSocket;
};

额外注意事项

  • 因接口继承QObject,Mock类和实现类都需添加Q_OBJECT宏,确保Qt的MOC工具能正确处理。
  • 若ServerUDP用到QUdpSocket的信号槽,需在IUdpSocket中声明对应信号和纯虚槽函数,Mock类可通过GMock的MOCK_SIGNAL或手动实现槽函数适配。

内容的提问来源于stack exchange,提问作者zenek martyniuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 14:36:19