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

GTest中测试调用多子函数的函数用例失败如何解决

问题根因

测试运行失败的核心原因很明确:onStartingExecute()内部调用的tt_init_device()、tt_init_port()、tt_init_pipeline()这类函数属于强依赖真实运行环境的底层接口,一般需要直连物理硬件、占用系统端口、加载完整业务管线的运行时资源,而GTest单元测试运行在普通宿主机环境,没有对应资源支撑,调用这类接口自然会触发崩溃、返回非预期错误,最终导致用例失败。

单元测试不需要真的执行底层初始化逻辑,核心验证目标是onStartingExecute()本身的逻辑正确性:也就是它有没有按约定顺序、约定次数调用所有要求的初始化子函数,子函数内部的具体实现不属于这个用例的测试范围,必须做依赖隔离。

标准测试实现方案(依赖注入+GMock)

这是工业界最常用的可维护测试方案,基于GTest配套的GMock框架完成依赖打桩:

1. 抽象底层初始化接口

首先把零散的全局tt_init_*函数封装成统一接口类,方便后续替换不同场景的实现:

// 初始化抽象接口
class ITtInitializer {
public:
    virtual ~ITtInitializer() = default;
    virtual int tt_init_device() = 0;
    virtual int tt_init_port() = 0;
    virtual int tt_init_pipeline() = 0;
    // 其余同类初始化函数统一在此声明
};

// 生产环境用的真实实现,直接包装原有全局函数
class RealTtInitializer : public ITtInitializer {
public:
    int tt_init_device() override { return ::tt_init_device(); }
    int tt_init_port() override { return ::tt_init_port(); }
    int tt_init_pipeline() override { return ::tt_init_pipeline(); }
};

2. 修改被测类支持依赖注入

给FusaTelltaleClientAppTh新增依赖注入入口,生产环境默认传入真实初始化实现,测试环境传入Mock对象:

class FusaTelltaleClientAppTh {
private:
    ITtInitializer* initializer_ = nullptr;
public:
    // 原有构造函数保留,生产环境默认加载真实初始化逻辑
    FusaTelltaleClientAppTh(int id, const std::string& name) 
        : FusaTelltaleClientAppTh(id, name, new RealTtInitializer()) {}
    
    // 测试用的构造函数,支持外部传入自定义初始化实现
    FusaTelltaleClientAppTh(int id, const std::string& name, ITtInitializer* initializer)
        : initializer_(initializer) {
        // 原有构造逻辑保持不变
    }

    void onStartingExecute() {
        // 原全局函数调用替换为接口实例调用
        initializer_->tt_init_device();
        initializer_->tt_init_port();
        initializer_->tt_init_pipeline();
        // 其余初始化调用按原有顺序补充
    }
};

3. 编写Mock类与测试用例

// Mock实现类
class MockTtInitializer : public ITtInitializer {
public:
    MOCK_METHOD(int, tt_init_device, (), (override));
    MOCK_METHOD(int, tt_init_port, (), (override));
    MOCK_METHOD(int, tt_init_pipeline, (), (override));
    // 其余初始化接口对应添加MOCK_METHOD声明
};

TEST_F(ICFusaTelltaleClientAppThTest, Test_onStartingExecute_InitFlowValid)
{
    // 初始化Mock对象,给所有接口设置默认返回成功的桩
    NiceMock<MockTtInitializer> mockInit;
    ON_CALL(mockInit, tt_init_device()).WillByDefault(Return(0));
    ON_CALL(mockInit, tt_init_port()).WillByDefault(Return(0));
    ON_CALL(mockInit, tt_init_pipeline()).WillByDefault(Return(0));

    // 传入Mock对象构造被测实例
    FusaTelltaleClientAppTh AppThobj(1, "abc", &mockInit);

    // 断言1:所有初始化函数均被调用恰好1次
    EXPECT_CALL(mockInit, tt_init_device()).Times(1);
    EXPECT_CALL(mockInit, tt_init_port()).Times(1);
    EXPECT_CALL(mockInit, tt_init_pipeline()).Times(1);

    // 断言2:初始化调用顺序严格符合 device -> port -> pipeline 的要求
    ::testing::Sequence initSeq;
    EXPECT_CALL(mockInit, tt_init_device()).InSequence(initSeq);
    EXPECT_CALL(mockInit, tt_init_port()).InSequence(initSeq);
    EXPECT_CALL(mockInit, tt_init_pipeline()).InSequence(initSeq);

    // 执行被测函数
    AppThobj.onStartingExecute();
}
  • 用NiceMock包装Mock对象可以屏蔽未设置EXPECT_CALL的接口调用报错,初始化函数较多时可以减少大量冗余代码
  • 如果业务不强制要求严格的初始化顺序,可以删除Sequence相关断言,只保留调用次数验证即可
临时快速验证方案(不推荐长期使用)

如果暂时不想重构代码做依赖注入,可以直接在测试文件中定义同名全局桩函数覆盖原有实现,让原有用例可以直接跑通:

// 测试文件内直接实现桩函数,注意C接口要加extern "C"
extern "C" {
    int tt_init_device() { return 0; }
    int tt_init_port() { return 0; }
    int tt_init_pipeline() { return 0; }
    // 其余初始化函数统一做空实现返回成功
}

// 原有测试用例无需修改即可运行
TEST_F(ICFusaTelltaleClientAppThTest,Test_onStartingExecute)
{
   FusaTelltaleClientAppTh AppThobj(1,"abc");
   AppThobj.onStartingExecute();
}

注意:这种全局桩的方式耦合度极高,一旦原函数签名、返回值约定变更,不会触发编译报错,只适合临时快速验证场景,长期迭代维护必须使用依赖注入+GMock的方案。

内容的提问来源于stack exchange,提问作者Kiran JP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:06:17