CMocka 是一款面向 C 语言的轻量级单元测试框架,核心支持Mock 对象模拟,特别适合嵌入式 / SOC 开发中的代码测试。
核心定位
它是从谷歌的 Cmockery 框架继承而来的工具,主打 **“仅依赖标准 C 库”**,能在 Linux、Windows、嵌入式等多平台运行,解决 C 代码(尤其是硬件依赖型代码)的 “单元测试难落地” 问题。
关键功能
Mock 对象模拟可以为依赖的硬件驱动、外部模块创建 “替身函数”:
- 预设函数的返回值 / 输出参数(比如模拟
uart_send()的成功返回,无需真实串口硬件); - 校验函数的调用次数、入参、调用顺序(比如确认
sensor_read()是否被正确调用)。
- 预设函数的返回值 / 输出参数(比如模拟
基础测试能力
- 提供丰富的断言宏(如
assert_int_equal验证数值相等); - 支持测试夹具(Setup/Teardown),统一管理测试的初始化 / 清理;
- 检测内存泄漏、缓冲区溢出等问题。
- 提供丰富的断言宏(如
适配嵌入式场景
- 仅依赖标准 C 库,可通过交叉编译适配嵌入式平台;
- 不使用
fork()等系统调用,适合资源受限的硬件环境。
典型用法(示例)
比如测试一个依赖 UART 的函数:
#include <cmocka.h> #include "uart.h" // 被依赖的模块 // 被测试函数:调用uart_send发送错误码 void report_error(int code) { uart_send(code); } // Mock替身函数(CMocka自动生成或手动定义) void uart_send(int code) { check_expected(code); // 校验入参是否符合预期 } // 测试用例 void test_report_error(void **state) { (void)state; expect_value(uart_send, code, 100); // 预设期望入参是100 report_error(100); // 执行被测试函数 } // 运行测试 int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_report_error), }; return cmocka_run_group_tests(tests, NULL, NULL); }优势
- 轻量无依赖,易集成到嵌入式项目;
- 同时支持单元测试和接口测试;
- 输出格式灵活(支持 TAP、JUnit XML,适配持续集成)。
一、环境准备(核心依赖)
CMocka 仅依赖:
- C 编译器(gcc/clang/arm-none-eabi-gcc)
- 构建工具(cmake 3.10+ 或 make)
- CMocka 源码(推荐稳定版 1.1.5)
步骤 1:下载 CMocka 源码
# 方式1:直接下载源码包(推荐) wget https://cmocka.org/files/1.1/cmocka-1.1.5.tar.xz tar -xvf cmocka-1.1.5.tar.xz # 方式2:git克隆(最新版) git clone https://git.cryptomilk.org/projects/cmocka.git二、编译集成(分 2 种场景)
场景 1:Linux 主机测试(x86/x64,最快验证)
# 1. 进入源码目录,创建编译目录 cd cmocka-1.1.5 mkdir build && cd build # 2. cmake配置(指定安装路径,避免污染系统) cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local/cmocka \ -DCMAKE_BUILD_TYPE=Release \ -DUNIT_TESTING=OFF # 关闭CMocka自身的单元测试 # 3. 编译+安装 make -j4 sudo make install场景 2:嵌入式交叉编译(如 ARM Cortex-M)
# 1. 定义交叉编译工具链(替换为你的工具链路径) export CROSS_COMPILE=arm-none-eabi- export CC=${CROSS_COMPILE}gcc export AR=${CROSS_COMPILE}ar export STRIP=${CROSS_COMPILE}strip # 2. cmake配置(核心:指定交叉编译) cmake .. -DCMAKE_INSTALL_PREFIX=/your/project/cmocka-arm \ -DCMAKE_BUILD_TYPE=Release \ -DUNIT_TESTING=OFF \ -DCMAKE_SYSTEM_NAME=Generic \ # 无操作系统(裸机) -DCMAKE_C_FLAGS="-mthumb -mcpu=cortex-m4" # 适配你的MCU架构 # 3. 编译+安装到项目目录 make -j4 make install关键说明:
- 安装后会生成:
- 头文件:
include/cmocka.h - 库文件:
lib/libcmocka.a(静态库,嵌入式优先用)
- 头文件:
三、项目集成(3 步接入你的代码)
假设你的嵌入式项目结构如下:
your_project/ ├── src/ # 业务代码(如norflash_test.c) ├── test/ # 测试代码 ├── cmocka/ # 存放CMocka的头文件+库文件 └── CMakeLists.txt步骤 1:复制 CMocka 文件到项目
# 复制头文件 cp /usr/local/cmocka/include/cmocka.h your_project/cmocka/ # 复制静态库(主机/嵌入式对应不同库) # 主机: cp /usr/local/cmocka/lib/libcmocka.a your_project/cmocka/ # 嵌入式: cp /your/project/cmocka-arm/lib/libcmocka.a your_project/cmocka/步骤 2:编写 CMakeLists.txt(核心)
cmake_minimum_required(VERSION 3.10) project(embedded_test) # 1. 引入CMocka include_directories(${PROJECT_SOURCE_DIR}/cmocka) # 头文件路径 link_directories(${PROJECT_SOURCE_DIR}/cmocka) # 库文件路径 # 2. 要测试的业务代码(如norflash驱动) add_library(business_code STATIC src/norflash.c) # 3. 测试代码(如test_norflash.c) add_executable(norflash_test test/test_norflash.c) # 链接CMocka和业务代码 target_link_libraries(norflash_test business_code cmocka) # 嵌入式额外配置(根据你的MCU调整) if(ARM_EMBEDDED) set(CMAKE_C_FLAGS "-mthumb -mcpu=cortex-m4 -ffreestanding -nostdlib") # 链接脚本、启动文件等 target_link_libraries(norflash_test ${PROJECT_SOURCE_DIR}/src/link.ld) endif()步骤 3:编写第一个测试用例(示例:NOR Flash 读写测试)
在test/test_norflash.c中:
#include <cmocka.h> #include "norflash.h" // 你的NOR Flash驱动头文件 // 测试夹具:每个用例执行前初始化 static int setup_norflash_test(void **state) { // 模拟NOR Flash初始化(无需真实硬件) norflash_init(); return 0; } // 测试夹具:每个用例执行后清理 static int teardown_norflash_test(void **state) { // 模拟释放资源 norflash_deinit(); return 0; } // 测试用例1:验证NOR Flash读操作 static void test_norflash_read(void **state) { uint8_t buf[4] = {0}; // 调用被测函数:读取地址0x0000的4字节 int ret = norflash_read(0x0000, buf, 4); // 断言:返回值应为0(成功) assert_int_equal(ret, 0); // 断言:读取的数据符合预期(模拟场景可预设) assert_memory_equal(buf, (uint8_t[]){0x11, 0x22, 0x33, 0x44}, 4); } // 测试用例2:验证Mock函数调用(模拟写操作) static void test_norflash_write(void **state) { uint8_t data[] = {0xaa, 0xbb}; // 预设:期望norflash_erase被调用1次,入参为0x1000 expect_value(norflash_erase, addr, 0x1000); expect_call_count(norflash_erase, 1); // 执行被测函数 norflash_write(0x1000, data, 2); // 校验:norflash_erase确实被调用 check_expected_calls(); } // 注册测试用例 int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_norflash_read, setup_norflash_test, teardown_norflash_test), cmocka_unit_test(test_norflash_write), }; // 运行所有测试 return cmocka_run_group_tests(tests, NULL, NULL); }四、运行验证
场景 1:Linux 主机运行
# 编译测试程序 cd your_project mkdir build && cd build cmake .. -DARM_EMBEDDED=OFF make -j4 # 运行测试 ./norflash_test✅ 成功输出示例:
[==========] Running 2 test(s). [ RUN ] test_norflash_read [ OK ] test_norflash_read [ RUN ] test_norflash_write [ OK ] test_norflash_write [==========] 2 test(s) passed.场景 2:嵌入式运行(裸机)
# 编译生成bin/hex文件 cmake .. -DARM_EMBEDDED=ON make -j4 # 下载到硬件(用你的烧录工具,如openocd/jlink) openocd -f interface/jlink.cfg -f target/stm32f4x.cfg -c "program norflash_test.hex verify reset exit"✅ 验证方式:通过串口 / 调试器查看测试输出(需在代码中适配 printf 到串口)。
总结
- 核心步骤:下载 CMocka → 编译(主机 / 交叉编译)→ 项目集成(头文件 + 库 + CMake)→ 编写测试用例 → 运行验证;
- 嵌入式关键:交叉编译时指定
CMAKE_SYSTEM_NAME=Generic和 MCU 架构参数,用静态库libcmocka.a; - 简化技巧:新手可先在 Linux 主机完成测试验证,再移植到嵌入式硬件。