3. 测试框架选型:CUnit、Unity、CMock、Ceedling、Google Test 对比分析

做嵌入式单元测试,第一步就是选框架。说实话,这步选错了,后面全是坑。我见过不少团队,框架换来换去,代码重构了好几轮,最后又回到原点。

今天我把市面上主流的几个框架拉出来遛遛。CUnit、Unity、CMock、Ceedling、Google Test,一个一个说清楚。你想想看,选框架其实就三个维度:资源开销、测试能力、生态配套。

核心观点:没有最好的框架,只有最适合你项目的框架。MCU 资源紧张就选 Unity,PC 端验证就上 Google Test,团队需要快速搭建就上 Ceedling。

3.1 框架全景对比

先看一张总览表。我按自己的经验,把每个框架的定位、优缺点、适用场景都列出来了。

框架 语言 资源开销 Mock 支持 构建系统 适用场景
CUnit C 中等 无 手动 Makefile Linux 下 C 项目
Unity C 极低 (~2KB) 需搭配 CMock 手动 / Ruby 脚本 MCU 裸机 / RTOS
CMock C 中等 自动生成 Mock Ruby 脚本 配合 Unity 使用
Ceedling C 中等 内置 CMock Ruby + Rake 嵌入式项目全流程
Google Test C++ 较高 内置 Mock CMake PC 端 / Linux 验证

嗯,这里要注意。表格里的「资源开销」指的是运行时开销。CUnit 和 Google Test 需要动态内存分配,在资源紧张的 MCU 上跑不动。Unity 就不一样了,纯宏实现,几乎不占额外资源。

3.2 逐个拆解

3.2.1 CUnit — 老牌框架,但有点重

CUnit 是 Linux 下 C 项目的经典选择。它提供了完整的测试套件管理、断言宏、测试报告输出。我个人习惯在 PC 端做算法验证时用它。

但说实话,CUnit 不太适合嵌入式。为什么?因为它需要 malloc 来管理测试注册表。我在项目中遇到过,把 CUnit 移植到 STM32 上,结果堆空间不够,跑几个测试就崩了。

避坑:CUnit 的测试注册是动态的,MCU 上慎用。如果你非要用,记得把堆调大,或者改用静态注册版本。

3.2.2 Unity — 嵌入式测试的标配

Unity 是我最推荐的嵌入式单元测试框架。它只有两个文件:unity.c 和 unity.h。全部用宏实现,没有动态内存分配,没有标准库依赖。

看一个最简单的例子:

#include "unity.h"

void setUp(void) {}
void tearDown(void) {}

void test_Add_ShouldReturnSum(void) {
    TEST_ASSERT_EQUAL_INT(5, add(2, 3));
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_Add_ShouldReturnSum);
    return UNITY_END();
}

你看,代码非常干净。没有复杂的初始化,没有动态注册。直接跑就完了。我曾经在一个 8KB RAM 的 MCU 上跑 Unity,一点问题没有。

小技巧:Unity 的断言宏支持自定义消息。比如 TEST_ASSERT_EQUAL_INT_MESSAGE(5, result, "加法结果不对"),调试时特别有用。

3.2.3 CMock — 自动生成 Mock 对象

做单元测试,Mock 是绕不开的。CMock 就是专门给 Unity 配的 Mock 生成工具。它读取头文件,自动生成 Mock 函数。

举个例子。假设你有一个硬件抽象层:

// hal.h
int hal_read_sensor(void);
void hal_set_pin(int pin, int value);

CMock 会生成:

void hal_read_sensor_ExpectAndReturn(int retval);
void hal_set_pin_Expect(int pin, int value);

然后你在测试里这样写:

void test_SensorRead_ShouldReturnMockedValue(void) {
    hal_read_sensor_ExpectAndReturn(42);
    int result = read_sensor_wrapper();
    TEST_ASSERT_EQUAL_INT(42, result);
}

嗯,这里要注意。CMock 是用 Ruby 写的,生成过程需要 Ruby 环境。我第一次用的时候,在 Windows 上折腾了半天环境。后来直接上了 Ceedling,省心多了。

3.2.4 Ceedling — 一站式解决方案

Ceedling 把 Unity、CMock、构建系统打包在一起。你只需要写测试代码,它帮你搞定编译、链接、运行、报告生成。

我个人习惯用 Ceedling 搭建新项目。初始化一个项目:

ceedling new my_project
cd my_project
ceedling test:all

就这两行命令,测试环境就搭好了。Ceedling 会自动扫描 test/ 目录下的测试文件,编译、链接、运行,一气呵成。

推荐:如果你是嵌入式团队,想快速落地单元测试,直接上 Ceedling。它把最繁琐的构建配置都封装好了,你只管写测试。

3.2.5 Google Test — 功能最全,但最重

Google Test 是 C++ 的测试框架,功能非常强大。它有丰富的断言、死亡测试、参数化测试、测试固件。但代价是体积大,依赖 C++ 运行时。

Google Test 适合在 PC 端做验证。比如你写了一个算法库,先在 PC 上用 Google Test 跑一遍,再交叉编译到嵌入式平台。我在项目中遇到过,用 Google Test 做算法验证,发现 bug 后直接在 PC 上调试,效率很高。

但别想着把它塞进 MCU。Google Test 需要 pthread、iostream,在裸机上根本跑不起来。

3.3 选型决策流程

说了这么多,到底怎么选?我画了一张决策流程图,你照着走一遍就知道了。

嵌入式单元测试框架选型决策流程 开始选型 目标平台是 MCU 还是 PC? MCU (资源受限) / PC (资源充足) MCU PC Unity + CMock Google Test 需要 Mock 吗? 是 上 Ceedling 否 纯 Unity 需要 Mock 吗? 是 GTest + GMock 否 纯 GTest 选型完成,开始搭建测试环境 下一章:环境搭建与第一个测试用例 MCU 资源紧张时优先 Unity PC 验证优先 Google Test

3.4 我的建议

说了这么多,总结一下我的个人经验:

  • MCU 裸机或 RTOS 项目:直接上 Unity + CMock。如果团队有 Ruby 环境,用 Ceedling 一键搭建。
  • PC 端算法验证:用 Google Test。功能全,社区活跃,文档丰富。
  • Linux 下 C 项目:CUnit 可以用,但我更推荐 Unity。轻量,移植方便。
  • 团队刚起步:别纠结,先选一个能跑起来的。我见过太多团队在选型上花了两周,最后连一个测试都没写出来。

我的经验:选框架就像选工具。别想着一步到位,先跑起来再说。等你写了 100 个测试用例,自然就知道哪个框架适合你了。