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

如何正确结合GMock、虚函数与Boost MSM?嵌入式测试难题求解

解决方案:避免生产代码生成VTable的Boost MSM测试方案

针对嵌入式场景下Boost MSM状态机的测试需求,以下几个方案可以在不引入生产代码VTable的前提下,完成状态机对静态类X方法调用逻辑的验证:

方案1:模板注入(Policy-Based Design)

将状态机设计为模板类,把负责UART命令生成的类作为模板参数传入。生产环境使用原静态类X,测试环境使用带GMock虚方法的Mock类——由于模板实例化是编译期决定的,生产代码中只会实例化使用静态类X的版本,完全不会生成VTable。

代码示例:

状态机模板定义

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>

// 原静态类X(生产代码用)
class X {
public:
    static void send_init_cmd() { /* 生产环境UART命令逻辑 */ }
    static void send_reset_cmd() { /* 生产环境UART命令逻辑 */ }
};

// 状态机前端定义(模板化)
template<typename CmdGenerator>
struct StateMachineFront : boost::msm::front::state_machine_def<StateMachineFront<CmdGenerator>> {
    // 状态定义
    struct InitState : boost::msm::front::state<> {};
    struct ResetState : boost::msm::front::state<> {};

    // 初始状态
    using initial_state = InitState;

    // 事件定义
    struct ResetEvent {};

    // 动作:调用CmdGenerator的静态方法
    struct CallInitCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
            CmdGenerator::send_init_cmd();
        }
    };

    struct CallResetCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
            CmdGenerator::send_reset_cmd();
        }
    };

    // 状态转换表
    using transition_table = boost::mpl::vector<
        boost::msm::front::Row<InitState, ResetEvent, ResetState, CallResetCmd, boost::msm::front::none>,
        boost::msm::front::Row<ResetState, boost::msm::front::none, InitState, CallInitCmd, boost::msm::front::none>
    >;
};

// 生产环境状态机实例
using ProductionStateMachine = boost::msm::back::state_machine<StateMachineFront<X>>;

测试代码(使用GMock)

#include <gmock/gmock.h>

// 测试用Mock类(仅在测试代码中编译)
class MockCmdGenerator {
public:
    MOCK_STATIC_METHOD0(send_init_cmd, void());
    MOCK_STATIC_METHOD0(send_reset_cmd, void());
};

// 测试环境状态机实例
using TestStateMachine = boost::msm::back::state_machine<StateMachineFront<MockCmdGenerator>>;

TEST(StateMachineTest, TestCmdCalls) {
    TestStateMachine fsm;
    fsm.start();

    // 验证初始状态调用send_init_cmd
    EXPECT_CALL(MockCmdGenerator, send_init_cmd()).Times(1);
    fsm.process_event(typename StateMachineFront<MockCmdGenerator>::ResetEvent{});

    // 验证状态转换后调用send_reset_cmd
    EXPECT_CALL(MockCmdGenerator, send_reset_cmd()).Times(1);
    fsm.process_event(typename StateMachineFront<MockCmdGenerator>::ResetEvent{});
}

方案2:条件编译切换实现

通过预编译宏区分生产和测试环境,生产环境直接调用静态类X的方法,测试环境调用全局Mock对象的方法。这种方式完全避免了模板,代码更简洁,且生产代码无任何虚函数开销。

代码示例:

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>

// 原静态类X
class X {
public:
    static void send_init_cmd() { /* 生产逻辑 */ }
    static void send_reset_cmd() { /* 生产逻辑 */ }
};

// 测试用Mock(仅测试环境编译)
#ifdef TEST_BUILD
#include <gmock/gmock.h>
class MockCmdGenerator {
public:
    MOCK_METHOD0(send_init_cmd, void());
    MOCK_METHOD0(send_reset_cmd, void());
};
extern MockCmdGenerator g_mock_cmd_gen;
#endif

// 状态机前端定义(非模板)
struct StateMachineFront : boost::msm::front::state_machine_def<StateMachineFront> {
    struct InitState : boost::msm::front::state<> {};
    struct ResetState : boost::msm::front::state<> {};
    using initial_state = InitState;
    struct ResetEvent {};

    struct CallInitCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
#ifdef TEST_BUILD
            g_mock_cmd_gen.send_init_cmd();
#else
            X::send_init_cmd();
#endif
        }
    };

    struct CallResetCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
#ifdef TEST_BUILD
            g_mock_cmd_gen.send_reset_cmd();
#else
            X::send_reset_cmd();
#endif
        }
    };

    using transition_table = boost::mpl::vector<
        boost::msm::front::Row<InitState, ResetEvent, ResetState, CallResetCmd, boost::msm::front::none>,
        boost::msm::front::Row<ResetState, boost::msm::front::none, InitState, CallInitCmd, boost::msm::front::none>
    >;
};

using StateMachine = boost::msm::back::state_machine<StateMachineFront>;

测试代码中初始化全局Mock

#include <gmock/gmock.h>
MockCmdGenerator g_mock_cmd_gen;

TEST(StateMachineTest, TestCmdCalls) {
    StateMachine fsm;
    fsm.start();

    EXPECT_CALL(g_mock_cmd_gen, send_init_cmd()).Times(1);
    fsm.process_event(StateMachineFront::ResetEvent{});

    EXPECT_CALL(g_mock_cmd_gen, send_reset_cmd()).Times(1);
    fsm.process_event(StateMachineFront::ResetEvent{});
}

方案3:Boost MSM动作适配器

利用Boost MSM的动作机制,将方法调用封装为可替换的函数对象,生产环境绑定静态类X的方法,测试环境绑定Mock的方法。这种方式无需修改状态机的核心结构,仅需替换动作实现。

代码示例:

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <functional>

// 原静态类X
class X {
public:
    static void send_init_cmd() { /* 生产逻辑 */ }
    static void send_reset_cmd() { /* 生产逻辑 */ }
};

// 动作适配器:封装可替换的命令调用逻辑
struct CmdActions {
    static std::function<void()> send_init_cmd;
    static std::function<void()> send_reset_cmd;
};

// 生产环境初始化动作
#ifndef TEST_BUILD
std::function<void()> CmdActions::send_init_cmd = [](){ X::send_init_cmd(); };
std::function<void()> CmdActions::send_reset_cmd = [](){ X::send_reset_cmd(); };
#endif

// 状态机前端定义
struct StateMachineFront : boost::msm::front::state_machine_def<StateMachineFront> {
    struct InitState : boost::msm::front::state<> {};
    struct ResetState : boost::msm::front::state<> {};
    using initial_state = InitState;
    struct ResetEvent {};

    struct CallInitCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
            CmdActions::send_init_cmd();
        }
    };

    struct CallResetCmd {
        template<typename Event, typename FSM, typename SourceState, typename TargetState>
        void operator()(const Event&, FSM&, SourceState&, TargetState&) {
            CmdActions::send_reset_cmd();
        }
    };

    using transition_table = boost::mpl::vector<
        boost::msm::front::Row<InitState, ResetEvent, ResetState, CallResetCmd, boost::msm::front::none>,
        boost::msm::front::Row<ResetState, boost::msm::front::none, InitState, CallInitCmd, boost::msm::front::none>
    >;
};

using StateMachine = boost::msm::back::state_machine<StateMachineFront>;

测试代码中替换动作

#include <gmock/gmock.h>

class MockCmdGenerator {
public:
    MOCK_METHOD0(send_init_cmd, void());
    MOCK_METHOD0(send_reset_cmd, void());
};

TEST(StateMachineTest, TestCmdCalls) {
    MockCmdGenerator mock;
    // 替换动作实现为Mock调用
    CmdActions::send_init_cmd = [&mock](){ mock.send_init_cmd(); };
    CmdActions::send_reset_cmd = [&mock](){ mock.send_reset_cmd(); };

    StateMachine fsm;
    fsm.start();

    EXPECT_CALL(mock, send_init_cmd()).Times(1);
    fsm.process_event(StateMachineFront::ResetEvent{});

    EXPECT_CALL(mock, send_reset_cmd()).Times(1);
    fsm.process_event(StateMachineFront::ResetEvent{});
}

方案选择建议

  • 如果项目已经采用模板化设计,优先选方案1,它的扩展性最好,测试和生产代码完全解耦。
  • 如果追求代码简洁、最小改动,选方案2,仅通过宏切换实现,生产代码无额外开销。
  • 如果不想修改状态机核心结构,选方案3,通过函数对象动态替换动作,灵活性较强。

内容的提问来源于stack exchange,提问作者Daniel S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:10:01