如何在嵌入式系统中进行C++单元测试?

来源:建站技术作者:广州SEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何在嵌入式系统中进行C++单元测试?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何在嵌入式系统中进行C++单元测试?》有用,将其分享出去将是对创作者最好的鼓励。

在嵌入式系统中开展C++单元测试,需要兼顾资源限制和硬件依赖特性,选择合适的测试方案才能高效验证代码逻辑的正确性。

如何在嵌入式系统中进行C++单元测试?

嵌入式C++单元测试的核心挑战

嵌入式系统通常存在内存空间小、CPU性能有限、缺少标准输入输出设备等特点,同时很多业务逻辑会和硬件寄存器、外设驱动直接绑定,这给单元测试带来了几个典型问题:

  • 无法直接使用PC端常见的测试框架,很多框架依赖标准库和动态内存分配
  • 硬件相关代码难以在测试环境中直接运行,需要隔离硬件依赖
  • 测试代码不能占用过多Flash和RAM空间,避免影响正常业务功能

测试框架的选择

针对嵌入式场景,优先选择轻量级、无依赖或者依赖极少的C++测试框架,以下是两类常见选择:

无依赖的轻量框架

这类框架通常只有几个头文件,不需要链接额外的库,适合裸机或者资源极小的嵌入式场景,比如Unity测试框架,它是纯C实现的轻量测试框架,也可以兼容C++代码,核心代码量只有几千行,不依赖标准库,只需要实现几个底层的输出接口即可运行。

适配嵌入式的C++测试框架

如果嵌入式系统支持C++标准库的部分特性,可以选择Google Test的嵌入式适配版本,或者Catch2的轻量配置模式,通过关闭不必要的特性来减少资源占用。

硬件依赖隔离方法

嵌入式代码中的硬件操作通常通过寄存器访问、驱动接口调用实现,要测试这些代码的逻辑,需要先隔离硬件依赖,常用的方法是接口抽象和Mock:

接口抽象

将硬件相关的操作封装成抽象接口,业务逻辑只依赖接口而非具体硬件实现,测试时传入模拟的实现即可。例如外设串口发送功能的抽象:

// 串口发送抽象接口
class UartInterface {
public:
    virtual ~UartInterface() = default;
    // 发送数据接口
    virtual void send(const uint8_t* data, uint16_t len) = 0;
};

// 真实硬件实现
class HardwareUart : public UartInterface {
public:
    void send(const uint8_t* data, uint16_t len) override {
        // 操作硬件寄存器发送数据
        for (uint16_t i = 0; i < len; ++i) {
            // 假设UART_TX_REG是硬件发送寄存器
            *reinterpret_cast<volatile uint8_t*>(0x40001000) = data[i];
        }
    }
};

Mock实现

测试时创建接口的Mock实现,记录调用参数和次数,方便验证业务逻辑是否正确调用了硬件接口:

// 串口的Mock实现
class MockUart : public UartInterface {
public:
    void send(const uint8_t* data, uint16_t len) override {
        // 记录调用参数
        call_count++;
        sent_data.assign(data, data + len);
    }

    // 测试用辅助方法
    uint32_t get_call_count() const { return call_count; }
    const std::vector<uint8_t>& get_sent_data() const { return sent_data; }

private:
    uint32_t call_count = 0;
    std::vector<uint8_t> sent_data;
};

测试代码组织与示例

测试代码需要和正常业务代码分离,通常放在独立的test目录下,编译时通过条件编译或者不同的编译目标来控制是否包含测试代码。以下是一个完整的测试示例,使用轻量的测试宏实现断言:

#include <cstdint>
#include <vector>
#include <cstring>

// 简单测试断言宏
#define TEST_ASSERT(cond) 
    do { 
        if (!(cond)) { 
            /* 这里可以对接底层的输出接口,比如串口打印错误信息 */ 
            test_fail_count++; 
            return; 
        } 
    } while(0)

// 前置声明的接口和Mock类(和上面的定义一致)
class UartInterface {
public:
    virtual ~UartInterface() = default;
    virtual void send(const uint8_t* data, uint16_t len) = 0;
};

class MockUart : public UartInterface {
public:
    void send(const uint8_t* data, uint16_t len) override {
        call_count++;
        sent_data.assign(data, data + len);
    }

    uint32_t get_call_count() const { return call_count; }
    const std::vector<uint8_t>& get_sent_data() const { return sent_data; }

private:
    uint32_t call_count = 0;
    std::vector<uint8_t> sent_data;
};

// 待测试的业务逻辑:数据打包后通过串口发送
class DataPacker {
public:
    DataPacker(UartInterface* uart) : uart_(uart) {}

    // 打包数据并发送,添加头部和校验
    void pack_and_send(const uint8_t* payload, uint16_t payload_len) {
        uint8_t buffer[256];
        // 添加头部 0xAA 0xBB
        buffer[0] = 0xAA;
        buffer[1] = 0xBB;
        // 拷贝 payload
        memcpy(buffer + 2, payload, payload_len);
        // 计算简单校验和
        uint8_t checksum = 0;
        for (uint16_t i = 0; i < payload_len; ++i) {
            checksum += payload[i];
        }
        buffer[2 + payload_len] = checksum;
        // 发送数据
        uart_->send(buffer, 2 + payload_len + 1);
    }

private:
    UartInterface* uart_;
};

// 测试失败计数
uint32_t test_fail_count = 0;

// 测试用例:验证打包发送功能
void test_data_packer() {
    MockUart mock_uart;
    DataPacker packer(&mock_uart);

    uint8_t payload[] = {0x01, 0x02, 0x03};
    packer.pack_and_send(payload, 3);

    // 断言调用次数
    TEST_ASSERT(mock_uart.get_call_count() == 1);
    // 断言发送数据长度
    TEST_ASSERT(mock_uart.get_sent_data().size() == 6);
    // 断言头部正确
    TEST_ASSERT(mock_uart.get_sent_data()[0] == 0xAA);
    TEST_ASSERT(mock_uart.get_sent_data()[1] == 0xBB);
    // 断言 payload 正确
    TEST_ASSERT(mock_uart.get_sent_data()[2] == 0x01);
    TEST_ASSERT(mock_uart.get_sent_data()[3] == 0x02);
    TEST_ASSERT(mock_uart.get_sent_data()[4] == 0x03);
    // 断言校验和正确:0x01+0x02+0x03=0x06
    TEST_ASSERT(mock_uart.get_sent_data()[5] == 0x06);
}

// 测试入口
int main() {
    test_data_packer();
    if (test_fail_count == 0) {
        // 测试通过,可对接硬件指示
    } else {
        // 测试失败,可对接硬件指示
    }
    return 0;
}

测试执行与资源优化

在嵌入式设备上运行测试时,可以通过以下方式优化资源占用:

  • 关闭测试框架的非必要特性,比如日志输出、异常支持等
  • 测试代码使用静态分配内存,避免动态内存申请
  • 将测试代码放在独立的Flash扇区,正式发布版本可以擦除该扇区减少固件体积
  • 对于无输出设备的嵌入式板,可以通过LED闪烁、GPIO电平变化来指示测试结果

总结

嵌入式系统中的C++单元测试核心是先隔离硬件依赖,再选择轻量适配的测试框架,通过接口抽象和Mock技术让业务逻辑可以在脱离硬件的环境中被验证。合理的测试组织可以降低后期调试成本,提升代码的可靠性,即使资源受限的嵌入式场景也可以落地基础的单元测试能力。

embedded_C++unit_testtest_frameworkmock修改时间:2026-06-12 19:24:21

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。