这一章我们要攻克嵌入式开发中最常见的"重头戏":通信协议栈。
不管是 UART、SPI 还是J1939/NMEA 2000等应用层协议,核心难点都在于:如何处理连续的数据流、缓冲区溢出以及复杂的结构体解析。
4.1 协议栈 TDD 的难点
在测试串口(UART)时,我们不仅要 Mock 硬件发送函数,还要模拟"对方发过来的数据"。
-
发送测试:验证逻辑是否将正确的字节序写入了驱动。
-
接收测试:向缓冲区注入模拟数据,验证解析算法(如帧头、校验位、帧尾)是否正确。
4.2 接口抽象:面向报文的驱动
在 src/Driver_UART.h 中,我们不直接操作寄存器,而是定义发送字节的接口:
#ifndef DRIVER_UART_H
#define DRIVER_UART_H
#include <stdint.h>
// 发送一个字节
void UART_SendByte(uint8_t data);
// 检查是否有数据收到
bool UART_IsDataAvailable(void);
// 获取收到的字节
uint8_t UART_GetByte(void);
#endif
4.3 实战:测试一个简单的命令解析器
需求: 协议格式为 [0xAA, CMD, DATA, 0x55]。 我们要实现一个 Protocol_Parse() 函数,当收到 0xAA 0x01 0x01 0x55 时,触发"点灯"动作。
第一步:编写测试 (test_Protocol.c) 这里我们要用到 Expect 的连贯调用,模拟一串字节的到来。
#include "unity.h"
#include "mock_Driver_UART.h"
#include "mock_Hardware_Interface.h"
#include "Protocol_Service.h"
void test_Should_ExecuteCommand_WhenValidFrameReceived(void) {
// 模拟收到一帧完整数据包:0xAA, 0x01, 0x01, 0x55
// 第一次调用:帧头
UART_IsDataAvailable_ExpectAndReturn(true);
UART_GetByte_ExpectAndReturn(0xAA);
// 第二次调用:命令码 0x01
UART_IsDataAvailable_ExpectAndReturn(true);
UART_GetByte_ExpectAndReturn(0x01);
// 第三次调用:数据位 0x01
UART_IsDataAvailable_ExpectAndReturn(true);
UART_GetByte_ExpectAndReturn(0x01);
// 第四次调用:帧尾 0x55
UART_IsDataAvailable_ExpectAndReturn(true);
UART_GetByte_ExpectAndReturn(0x55);
// 关键:当解析到完整有效帧后,预期硬件接口会执行点灯
HW_GPIO_SetLed_Expect(true);
// 执行解析函数(模拟在 while(1) 或中断中调用多次)
for(int i=0; i<4; i++) {
Protocol_Parse();
}
}
4.4 进阶技巧:如何测试发送大块结构体?
如果你要发送一个复杂的 J1939 报文结构体,可以使用 CMock 的 ExpectWithArray 功能。
源码接口: void UART_WriteBuffer(uint8_t* data, uint16_t len);
测试代码:
void test_Should_SendCorrectPacket(void) {
uint8_t expected_data[] = {0x18, 0xFE, 0xF1, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};
// 告诉 CMock:我预期 UART_WriteBuffer 被调用
// 并且第一个参数(指针)指向的内容应该匹配 expected_data
UART_WriteBuffer_ExpectWithArray(expected_data, 8, 8);
J1939_SendWheelSpeed(0xAABBCCDD);
}
4.5 本章核心心法:协议栈的"状态机测试"
在专栏中,你可以重点给读者分享以下观点:
-
分层治之:驱动层(UART_Send)用 Mock 模拟;解析层(CRC校验、状态机转换)在 PC 上纯逻辑测试。
-
错误注入 :在测试里故意返回一个错误的校验码
0x99,验证解析器是否能正确丢弃非法包并复位状态机。 -
压力测试:在测试里模拟缓冲区瞬间塞满 1024 字节,观察逻辑是否会死锁。
本章小结
这一章我们攻克了"流式数据"的 TDD 难关。你现在已经有能力在不连串口线的情况下,把 J1939、NMEA 2000 或者 Modbus 的协议逻辑写得天衣无缝了。