1. 核心工具链安装(Windows 环境)
嵌入式开发者大多在 Windows 下工作,建议采用以下路径:
-
Ruby : 下载安装 RubyInstaller with Devkit。
- 关键点 :安装后运行
ridk install,选择 1, 2, 3 来配置 MSYS2 开发环境。
- 关键点 :安装后运行
-
Ceedling: 打开终端(CMD 或 PowerShell):gem install ceedling
-
GCC : 确保
gcc在你的系统路径中。你可以用 MinGW,或者直接使用 STM32CubeIDE 自带的arm-none-eabi-gcc(注:但在 PC 上跑单元测试,我们通常直接用 MinGW 的gcc编译成原生 exe)。
2. 将 Ceedling 植入 STM32 工程
假设你的工程名叫 MyProject,在根目录下运行:
ceedling new .
注意: 这里的 . 会在当前目录生成文件,而不是新建文件夹。它会生成:
-
project.yml:核心配置。 -
vendor/:里面包含了 Unity、CMock 和 Ceedling 的源码。这意味着你的项目是自包含的,别人拉下代码不需要再装 Ruby 也能跑(只要有 GCC 和 Ruby 环境)。
3. 深度定制 project.yml
这是部署最关键的一步。为了让 Ceedling 找到 STM32 的代码,你需要修改几个关键点:
A. 路径映射
STM32CubeIDE 的代码通常在 Core/Src,你得告诉 Ceedling:
:paths:
:test:
- +:test/**
:source:
- Core/Src/**
:include:
-
Core/Inc/**
-
Drivers/CMSIS/Device/ST/STM32G0xx/Include # 必须加上,否则找不到寄存器定义
B. CMock 自动化配置
这是让 Mock 变得好用的秘诀:
:cmock:
:mock_prefix: mock_
:when_no_prototypes: :warn
:enforce_strict_ordering: TRUE # 强制检查函数调用顺序,对协议栈测试非常有用
:plugins:
-
:ignore
-
:expect_any_args
-
:return_thru_ptr # 关键:用于 Mock 那些通过指针返回数据的函数(如 HAL_UART_Receive)
4. "硬件库"处理:Dummy Header 技巧
这是部署中最硬核的一点: STM32 的一些底层头文件包含了很多只有 ARM 编译器才认识的指令(如 __PACKED、__IO)。原生 GCC 编译测试时会报错。
解决方案: 在 test/support 文件夹下创建一个 stm32_fix.h,定义这些宏:
#ifndef STM32_FIX_H
#define STM32_FIX_H
#define __IO volatile
#define __PACKED
// 其他报错的宏统统在这里定义为空
#endif
然后在 project.yml 的 :test_preprocess 里包含它。
5. 验证部署是否成功
在命令行输入:
ceedling summary
如果你能看到:
Project: MyProject
Test Files: 0
Total Tests: 0
说明你的环境已经原地起飞。
附录:STM32 项目专属 project.yml 完整模板
这份配置文件针对 STM32 的目录结构和 C 语言特性进行了深度优化。建议将此内容保存在项目根目录。
--- Ceedling Project Configuration ---
:project:
:use_exceptions: FALSE
:use_test_preprocessor: TRUE # 开启预处理,解决复杂宏定义问题
:use_auxiliary_list: FALSE
:build_root: build # 所有的测试编译产物都在这里
:test_file_prefix: test_
:release_build:
:output: build/release/MyProject.out
:environment: \[\]
:extension:
:executable: .exe
:paths:
:test:
- +:test/**
:source:
- Core/Src/**
:include:
-
Core/Inc/**
-
Drivers/CMSIS/Device/ST/STM32G0xx/Include
-
Drivers/CMSIS/Include
-
test/support # 存放修复硬件宏定义的头文件
:defines:
模拟在 PC 环境下运行,定义一些 STM32 必要的宏
:common: &common_defines
-
STM32G031xx
-
UNIT_TEST
:test:
- *common_defines
:test_preprocess:
- *common_defines
:cmock:
:mock_prefix: mock_
:when_no_prototypes: :warn
:enforce_strict_ordering: TRUE
:plugins:
-
:ignore
-
:expect_any_args
-
:return_thru_ptr # 处理 HAL 库指针传参的关键插件
-
:callback # 处理中断回调测试的关键插件
:treat_as:
uint8: HEX8
uint16: HEX16
uint32: UINT32
int8: INT8
bool: UINT8
编译标志:针对 GCC 环境优化
:flags:
:test:
:compile:
:executable: gcc
:args:
-
-Wall
-
-Wno-address
-
-std=c99
-
-g
:plugins:
:load_paths:
- "#{Ceedling.linker.tools_path}"
:enabled:
-
stdout_pretty_tests_report
-
module_generator
-
gcov # 开启覆盖率分析
总结:TDD 的核心
-
隔离是第一生产力:无法测试是因为耦合太深。解耦不仅是为了测试,更是为了模块化。
-
不要过度 Mock:只 Mock 你的被测对象(SUT)之外的东西。如果一个函数纯粹是逻辑运算,直接测,别 Mock。
-
测试是最好的文档 :半年后你忘记了某个协议怎么解析,看一眼
test_protocol.c里的Expect序列,你就全明白了。