第五章:如何对 HAL 库本身进行单元测试?

这一章我们将深入嵌入式开发最"底层"的阵地。很多开发者会问:"逻辑层可以隔离,但我直接配置 STM32 寄存器或调用 HAL 库的代码,怎么测?"

这一章,我们要处理那些无法回避的底层调用,并引入 TDD 的进阶手段:Fake(伪函数)。

5.1 为什么 Mock 有时不够用?

对于 HAL_GPIO_WritePin 这种简单的一对一调用,Expect 很好用。但如果你在写一个 I2C 扫描逻辑,代码里涉及大量的循环和连续调用,写几十个 Expect 会让测试脚本极其冗长且难以维护。

此时,我们需要 Fake(伪造实现):我们写一个功能简单的函数,替换掉真正的 HAL 函数,并让它记录下所有的动作。

5.2 实战:测试一个 I2C 设备探测器

需求: 扫描 I2C 总线上地址从 1 到 127 的设备,并返回第一个响应的地址。

底层接口 (stm32g0xx_hal.h) : HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

5.3 编写测试:使用 Stub(存根/钩子)

Ceedling 的 CMock 支持 StubWithCallback。我们可以定义一个自定义函数,让 CMock 在被调用时运行我们的代码。

#include "unity.h"

#include "mock_stm32g0xx_hal_i2c.h" // 模拟 HAL 库

#include "I2C_Scanner.h"

// 这是一个伪造的 I2C 响应函数

// 我们模拟只有地址 0x50 的设备有响应

HAL_StatusTypeDef my_I2C_Stub(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout, int num_calls) {

if (DevAddress == (0x50 << 1)) {

return HAL_OK;

}

return HAL_ERROR;

}

void test_Scanner_Should_Return0x50_WhenDeviceFound(void) {

// 告诉 CMock:当 HAL_I2C_IsDeviceReady 被调用时,别管 Expect 了,去运行 my_I2C_Stub

HAL_I2C_IsDeviceReady_StubWithCallback(my_I2C_Stub);

uint8_t found_addr = I2C_Scanner_ScanFirst();

TEST_ASSERT_EQUAL_HEX8(0x50, found_addr);

}

5.4 进阶技巧:针对 STM32 寄存器的"影子内存"

如果你在写非常底层的代码(如手写寄存器驱动),可以利用一种叫 Shadow Register 的技术:

  1. 在测试环境中,定义一组全局变量(如 uint32_t V_GPIOA_ODR)。

  2. 在头文件中通过宏定义将 GPIOA->ODR 指向这个变量。

  3. 测试方法 :调用你的驱动函数,然后断言 TEST_ASSERT_EQUAL_HEX32(0x01, V_GPIOA_ODR)。

这让你可以验证驱动程序是否真的改写了对应的寄存器位。

5.5 本章核心心法:什么时候该停止测试?

请注意:

  • 不要去测试 ST 官方的 HAL 库 :我们默认 HAL_GPIO_WritePin 是工作的。

  • 测试的是"调用行为":我们要测的是"当传感器数据溢出时,驱动是否正确调用了复位寄存器"。

  • 避免过度 Mock:如果你的测试代码比业务代码长 5 倍,说明你的函数拆分得还不够细。

本章小结

我们掌握了 Stub 和 Callback,这让我们能够处理复杂的循环调用和底层库交互。至此,你已经完成了从业务逻辑到硬件驱动的全路径 TDD 覆盖。

相关推荐
mmmmath_33 小时前
LeetCode.104.二叉树的最大深度
数据结构·leetcode
花间相见3 小时前
【计算基础|网络14】网络层:IP、ICMP 与 ping
网络·网络协议·tcp/ip
Tronlong创龙3 小时前
SBC‑TLT153 嵌入式 Linux 应用开发,交叉编译与 GDB 调试实践(上)
嵌入式硬件·嵌入式开发·硬件开发·工业控制·工业开发板
巴渝小禹4 小时前
单片机死循环delay_us()计算方法
单片机·嵌入式硬件
Yiran_G4 小时前
LoRa串口模组和SoC模组怎么选?从MCU架构看无线通信方案设计
嵌入式硬件·物联网
HQB20265 小时前
CN6150|简芯维尔 6V/500mA 高速低噪声 CMOS‑LDO 线性稳压器
嵌入式硬件·硬件工程
半杯咖啡半行码5 小时前
Linux 驱动开发入门:内核模块编程基础
linux·服务器·网络
智能直播5 小时前
RK3528 V4L2 框架 UVC 接口自动获取 H.264 / MJPEG / YUYV 格式:从内核采集到硬解分发
java·网络·实时音视频·视频编解码·h.264
科芯创展6 小时前
XZ3445 高集成升压/升降压DC-DC电源芯片技术解析
单片机·嵌入式硬件