6.2 KiB
6.2 KiB
嵌入式测试开发标准流程(基于 STM32F4 + CH395F 经验)
适用范围:STM32 裸机/FreeRTOS 项目,Keil MDK-ARM v5 (ARMCC),串口日志输出测试结果。
1. 测试规划(文档先行)
在 docs/ 下创建测试规范文档,定义以下结构:
1.1 阶段概览表
| 阶段 ID | 类型 | 耗时 | 入口 | 出口 |
|---|---|---|---|---|
| TM-PHY-01 | 独立运行 | ~1s | 宏启用 | 串口汇总 |
1.2 每个测试用例
| 字段 | 说明 |
|---|---|
| ID | TC-NET-NNN,唯一编号 |
| 优先级 | P0(核心)/ P1(重要) |
| 类型 | 功能测试 / 边界测试 / 负向测试 / 压力测试 / 恢复测试 |
| 标题 | 一句话描述 |
| 前置条件 | 硬件状态、PC 端命令、依赖的其他用例 |
| 测试步骤 | 编号操作序列 |
| 成功标准 | 精确到"返回值 == 0x00"、"延迟 >= 60000ms" |
| 失败标准 | 每种失败对应的现象 |
| 覆盖原则 | 对应哪个设计原则 |
关键原则:先写判定标准,再写测试代码,避免"测了但不知道算不算过"。
2. 测试框架搭建
2.1 目录结构
test/
ch395f_test.h # 宏定义 + TEST_CHECK/TEST_REPORT + test_stats_t
ch395f_test_task.h # 仅导出 StartCh395fTestTask
ch395f_test_task.c # 全 Phase 实现
2.2 模板文件
ch395f_test.h — 宏+类型
// 阶段使能开关(取消注释即启用)
#define ENABLE_PHASE1_TESTS
// 测试统计
typedef struct {
uint16_t total;
uint16_t passed;
uint16_t failed;
} test_stats_t;
extern test_stats_t g_test_stats;
#define TEST_CHECK(cond, fmt, ...) do { \
g_test_stats.total++; \
if (cond) { \
g_test_stats.passed++; \
DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); \
} else { \
g_test_stats.failed++; \
DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); \
} \
} while (0)
#define TEST_REPORT(name) do { \
DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \
name, g_test_stats.passed, g_test_stats.total, g_test_stats.failed); \
} while (0)
ch395f_test_task.c — 每个 Phase 的模板
#ifdef ENABLE_PHASEX_TESTS
/*
* Phase X — 功能说明
*
* 测试目的:(概括)
* 前置条件:(硬件/软件/PC 端)
* 通过:(整体判定条件)
* 失败:(整体判定条件)
*/
static void phaseX_run(void) {
DBG_INFO("=== CH395F Phase X Tests ===");
/* ---- TC-NET-NNN: 用例标题 ---- */
{
// 原理说明
// 通过:精确条件
// 失败:精确条件
ret = some_api();
DBG_INFO(" expect: ...");
DBG_INFO(" actual: ...");
TEST_CHECK(ret == EXPECTED, "description");
}
TEST_REPORT("Phase X");
}
#endif
3. 编码规范
3.1 DBG_INFO 三行输出
每个判定点输出三行,一眼看出"期望什么、拿了什么、过没过":
[P1-01] ch395f_check_exist()
expect: 0x00 (~0x57 = 0xA8)
actual: 0x00
[PASS] P1-01 check_exist = 0x00 (expect 0x00)
3.2 硬编码值加注释
// 好:
ch395f_status_t ret = ch395f_check_exist();
// SPI 写入 0x06 (CMD_CHECK_EXIST) + 0x57 (测试字节)
// 成功:回复 == ~0x57 == 0xA8(CH395F_ERR_SUCCESS = 0x00)
// 失败:回复 != 0xA8(CH395F_STATUS_NOT_DETECTED = 0xFF)
// 不好:
ch395f_status_t ret = ch395f_check_exist();
TEST_CHECK(ret == 0, "check_exist OK");
3.3 无中文字符串
ARMCC v5 不识别 UTF-8 多字节字符。DBG_INFO/DBG_ERROR 中只写 ASCII。注释可以写中文。
3.4 栈安全
大缓冲区(如 64KB)用 static 全局,不放任务栈:
#define TEST_BUF_SIZE 65536
static uint8_t s_rx_buf[TEST_BUF_SIZE];
3.5 条件编译消除未使用变量警告
static uint8_t s_rx_buf[TEST_BUF_SIZE];
#if defined(ENABLE_PHASE5_TESTS) || defined(ENABLE_PHASE8_TESTS)
static uint8_t s_tx_buf[TEST_BUF_SIZE];
#endif
4. 统一入口
4.1 所有测试在 FreeRTOS 任务中串行执行
ch395fTestTask:
1. 等待 g_net_ready(netTask 初始化完成)
2. 阻塞执行 Phase 1/2/3/6/9(内部循环,无需外部连接)
3. 创建 TCP 监听 Socket(端口 8080,单连接模式)
4. 事件驱动循环 Phase 4/5/7/8/10(等待 PC 连接)
4.2 不要在 main.c 中直接调用测试
main.c 只做硬件初始化和启动 OS,不包含任何测试逻辑。
4.3 测试文件的手动注册
新测试文件需:
- 添加到
test/目录 - 修改
MDK-ARM/STM32F407-Demo.uvprojx添加文件引用和 IncludePath
5. 文档与代码同步
- 文档中的入口函数名、宏名、流程描述必须与代码一致
- 代码变更后立即更新文档,否则文档两天内就会失效
- 测试用例的通过/失败标准在文档和代码注释中都写清楚
- 文档使用标准 Markdown 表格,避免复杂嵌套导致渲染异常
6. 编译验证
6.1 底线
0 Error(s), 0 Warning(s),不可妥协。
6.2 常见警告处理
| 警告 | 原因 | 解决 |
|---|---|---|
#177-D: variable was declared but never referenced |
条件编译导致 | 加 #ifdef 包裹变量声明 |
#870-D: invalid multibyte character sequence |
DBG_INFO 中有中文 | 改为纯 ASCII |
| 隐式类型转换 | 参数类型不匹配 | 加显式 (uint8_t) 等 cast |
6.3 构建命令
MDK-ARM\build.bat
:: 或项目根目录执行 @build
退出码:0 = 成功 | 1 = 有警告(不通过)| 2+ = 错误
7. 迭代节奏
文档(规划 + 判定标准)
→ 代码(添加 Phase 实现)
→ @build(0 Error(s), 0 Warning(s))
→ 烧录验证(串口观察 PASS/FAIL)
→ 根据实际结果修正判定标准
→ 更新文档(保持同步)
→ 下一阶段
附录:本项目的测试文件清单
| 文件 | 作用 |
|---|---|
test/ch395f_test.h |
测试宏、类型定义、Phase 使能开关 |
test/ch395f_test_task.h |
导出 StartCh395fTestTask |
test/ch395f_test_task.c |
全 10 个 Phase 实现 |
docs/CH395F_Test_Guide.md |
测试规范、用例表格、通过/失败标准 |
docs/CH395F_Trap_Records.md |
已知硬件/软件陷阱及修复 |