第二章:隔离硬件 —— 利用 CMock 伪造 GPIO 与定时器

第一章我们跑通了环境,但这只是"纯软件"的逻辑。在嵌入式开发中,最让人头疼的是代码里随处可见的 HAL_GPIO_WritePin、__HAL_TIM_GET_COUNTER 等硬件依赖。

如果直接把这些带进测试,编译器会因为找不到 STM32 的寄存器定义而报错。这一章,我们要学习 TDD 的核心技术------隔离(Isolation)与伪装(Mocking)。

2.1 依赖倒置:不要直接调用 HAL 库

很多人的代码是这么写的:

// 坏习惯:业务逻辑和 HAL 库强耦合

void Toggle_Led_If_Expired(void) {

if (HAL_GetTick() > 1000) {

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

}

}

这段代码在 PC 上无法通过编译 ,因为 PC 没有 GPIOA。

TDD 的思维: 我们不直接调用底层,而是定义一套"硬件接口层"。 在 src/ 下创建 Hardware_Interface.h:

#ifndef HARDWARE_INTERFACE_H

#define HARDWARE_INTERFACE_H

#include <stdint.h>

#include <stdbool.h>

// 抽象后的接口,PC 和 STM32 都能识别

void HW_GPIO_SetLed(bool on);

uint32_t HW_GetSystemTick(void);

#endif

2.2 CMock 的魔法:mock_ 前缀

这是 Ceedling 最强大的功能。只要你在测试文件中 #include "mock_Hardware_Interface.h",Ceedling 就会:

  1. 扫描 Hardware_Interface.h。

  2. 自动生成 一个 mock_Hardware_Interface.c 文件。

  3. 伪造 出 HW_GPIO_SetLed_Expect 这种函数。

2.3 实战:编写一个延时点灯逻辑

我们要实现一个功能:调用 App_LightControl() 时,如果系统时间大于 500ms,就熄灭 LED。

第一步:编写测试 (test_App_Logic.c) 在 test/ 目录下创建文件。注意,我们先写测试,此时 App_LightControl 甚至还没被定义。

#include "unity.h"

#include "mock_Hardware_Interface.h" // 自动生成的 Mock 接口

#include "App_Logic.h"

void test_ShouldTurnOffLed_WhenTimeExceeds500ms(void) {

// 模拟上帝视角:

// 1. 我们预期逻辑会询问当前时间,我们让它返回 501

HW_GetSystemTick_ExpectAndReturn(501);

// 2. 因为 501 > 500,我们预期逻辑会调用 LED 设置函数,参数为 false (关灯)

HW_GPIO_SetLed_Expect(false);

// 3. 执行被测动作

App_LightControl();

}

void test_ShouldKeepLedOn_WhenTimeIsBelow500ms(void) {

// 1. 模拟时间还没到 500ms,返回 100

HW_GetSystemTick_ExpectAndReturn(100);

// 2. 这里不写 HW_GPIO_SetLed_Expect

// 如果代码在这个时候调用了该函数,测试会立即报错(Unexpected Call)

App_LightControl();

}

2.4 第二步:实现逻辑使测试通过

在 src/ 中创建 App_Logic.c:

#include "App_Logic.h"

#include "Hardware_Interface.h"

void App_LightControl(void) {

if (HW_GetSystemTick() > 500) {

HW_GPIO_SetLed(false);

}

}

2.5 深度解析:为什么这很重要?

  1. 确定性测试:在真实 STM32 上,你需要等 500ms 才能看到结果。在 Mock 环境下,时间是由你注入的。

  2. 硬件无关性:这段代码不需要连接任何开发板,在地铁上用笔记本就能完成开发。

  3. 接口契约 :这强迫你思考"我的逻辑到底需要哪些硬件资源",并把它们提炼成接口,从而实现了高内聚、低耦合。

本章小结

我们学会了如何使用 Expect(预期调用)和 ExpectAndReturn(预期调用并注入返回值)。这不仅能 Mock GPIO,还能 Mock 传感器读取、Flash 写入等一切硬件行为。

相关推荐
硅基手札3 分钟前
【串口技术系列文档 09】FIFO管理与流控
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札3 分钟前
【串口技术系列文档 08】中断驱动vs轮询vs DMA
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
尼喃17 分钟前
OVP芯片完整方案:前级瞬态钳位、持续过压隔离与后端保护
嵌入式硬件
不会代码的小猴21 分钟前
认识HAL库
单片机·嵌入式硬件
莫小墨33 分钟前
数字地、模拟地、功率地
嵌入式硬件
Zw-awa37 分钟前
把 AO 接回来:黑线和白底之间到底差了多少
stm32
BSD_HY42 分钟前
薄膜开关矩阵按键的GPIO扩展芯片选型与中断响应延迟
单片机·线性代数·矩阵
xunyan62341 小时前
51 单片机LED闪烁(SDCC + Notepad++ + STC-ISP/普中ISP)
单片机·notepad++·接口隔离原则
桀人1 小时前
USART帧格式
网络·stm32·单片机·嵌入式硬件
qq_448011161 小时前
如何烧录TAU804的固件
单片机·嵌入式硬件