3、单元测试框架选型:CUnit、Unity、CMock、Google Test 在嵌入式中的对比与选择

做嵌入式开发这么多年,我踩过最大的坑之一,就是项目做到一半才发现测试框架选错了。你想想看,代码写了几万行,突然发现框架对ARM Cortex-M支持不好,或者Mock功能太弱,那真是欲哭无泪。

今天我就把四个主流框架——CUnit、Unity、CMock、Google Test——掰开揉碎了讲清楚。每个框架我都实际用过,有些坑是我真金白银换来的经验。

3.1 先说说我的选型原则

嵌入式单元测试框架,说白了就三个硬指标:

  • 资源占用:能不能在目标板上跑?还是只能在PC上模拟?
  • Mock能力:能不能模拟硬件外设?比如ADC、UART、GPIO?
  • 集成难度:要不要改构建系统?要不要装额外的工具链?

我个人习惯,先把这三个条件列出来,再去看框架。不然很容易被花里胡哨的功能带偏。

3.2 CUnit:老牌框架,但有点「重」

CUnit是最早的一批C语言单元测试框架。它的设计思路是从JUnit抄过来的,所以用起来有种Java的味道。

优点

  • 功能完整:断言、测试套件、测试注册,该有的都有
  • 文档丰富:毕竟是老牌,网上资料一搜一大把
  • 支持多种输出格式:XML、文本、控制台

缺点

  • 体积大:完整编译下来,ROM占用轻松超过50KB
  • 动态内存分配:内部用了大量malloc,这在嵌入式里是大忌
  • 依赖标准库:很多MCU的libc实现不完整,跑不起来

我曾经在一个STM32F103项目里试过CUnit,结果光是初始化就占掉了8KB RAM。嗯,那时候我就知道,这玩意儿只适合在PC上做单元测试,别想着往板子上搬。

适用场景:PC端模拟测试、Linux环境、资源充足的嵌入式Linux系统。

3.3 Unity:嵌入式测试的「轻骑兵」

Unity是专门为嵌入式设计的测试框架。它只有一个.c文件和一个.h文件,加起来不到2000行代码。我第一次看到它的时候,心里想的是:「就这?」

但用起来才发现,轻量就是王道。

优点

  • 极轻量:ROM占用不到10KB,RAM几乎不占
  • 纯C实现:没有任何动态内存分配
  • 可移植性极强:从8位AVR到32位ARM都能跑
  • 配合CMock使用:Mock能力直接拉满

缺点

  • 功能简单:没有测试套件嵌套,没有参数化测试
  • 输出格式单一:默认只有文本输出
  • 社区相对小:遇到问题可能得自己啃源码

我现在的习惯是:只要项目是裸机或者RTOS,首选Unity。它可以在目标板上直接跑,也可以交叉编译后在PC上模拟。你想想看,这有多方便?

适用场景:裸机项目、RTOS项目、资源受限的MCU(如STM32、AVR、MSP430)。

3.4 CMock:让Mock变得「无痛」

CMock是Unity的黄金搭档。它的作用很简单:自动生成Mock函数。你只需要提供一个头文件,CMock就能生成对应的Mock实现。

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

// hal_adc.h
int HAL_ADC_Read(uint8_t channel);

用CMock生成Mock后,你就可以这样写测试:

#include "mock_hal_adc.h"

void test_read_sensor(void) {
    // 设置Mock返回值
    HAL_ADC_Read_ExpectAndReturn(1, 1024);
    
    // 调用被测函数
    int result = read_sensor(1);
    
    // 验证结果
    TEST_ASSERT_EQUAL(1024, result);
}

我曾经在一个项目里手动写了200多个Mock函数,写到后面整个人都麻了。后来换成CMock,一天就搞定了。嗯,工具选对了,效率翻倍。

注意事项:CMock依赖Ruby环境,生成Mock时需要额外一步。建议集成到构建系统中,比如CMake或者Makefile里自动调用。

3.5 Google Test:功能最强,但「水土不服」

Google Test(简称gtest)是C++的测试框架,但也可以用来测C代码。它的功能是四个框架里最强的:参数化测试、死亡测试、测试固件、Mock框架(Google Mock)……

但问题也出在这里:它太「重」了。

优点

  • 功能极其丰富:你能想到的测试场景,它基本都支持
  • 社区庞大:遇到问题,StackOverflow上一搜就有答案
  • 与Google Mock无缝集成:Mock能力一流

缺点

  • 依赖C++标准库:很多嵌入式工具链不支持完整的C++
  • 体积大:编译后轻松超过100KB
  • 交叉编译复杂:需要自己配置工具链和链接脚本

我个人建议:如果你的项目是用C++写的,或者你可以在PC上做大部分测试,那gtest是首选。但如果你想在目标板上跑单元测试,还是老老实实用Unity吧。

适用场景:嵌入式Linux项目、C++项目、PC端模拟测试。

3.6 横向对比:一张表说清楚

特性 CUnit Unity CMock Google Test
语言 C C C C++
ROM占用 ~50KB ~10KB ~5KB ~100KB+
动态内存
Mock支持 手动 手动 自动生成 Google Mock
目标板运行 困难 容易 容易 困难
学习曲线 中等 中等
社区活跃度 中等 中等

3.7 我的选型建议

说了这么多,到底怎么选?我总结了一个简单的决策树:

  1. 裸机/RTOS + 资源受限 → Unity + CMock
  2. 嵌入式Linux + C语言 → CUnit 或 Unity
  3. 嵌入式Linux + C++ → Google Test + Google Mock
  4. PC端模拟测试 → Google Test(功能最强)

避坑指南:我曾经在一个项目里选了CUnit,结果发现目标板跑不起来,最后只能把所有测试挪到PC上。但PC上模拟和实际硬件行为有差异,导致好几个bug没测出来。从那以后,我坚持「能上板就跑上板」的原则。

另外,不管你选哪个框架,都建议把测试集成到CI/CD流水线里。我见过太多人「今天写测试,明天忘测试」,最后测试代码比业务代码还烂。

好了,框架选型就聊到这里。下一章我会讲如何用Unity + CMock搭建一个完整的嵌入式单元测试工程,到时候咱们手把手过一遍。