245 lines
6.2 KiB
Markdown
245 lines
6.2 KiB
Markdown
# 嵌入式测试开发标准流程(基于 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` — 宏+类型
|
||
|
||
```c
|
||
// 阶段使能开关(取消注释即启用)
|
||
#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 的模板
|
||
|
||
```c
|
||
#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 硬编码值加注释
|
||
|
||
```c
|
||
// 好:
|
||
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` 全局,不放任务栈:
|
||
|
||
```c
|
||
#define TEST_BUF_SIZE 65536
|
||
static uint8_t s_rx_buf[TEST_BUF_SIZE];
|
||
```
|
||
|
||
### 3.5 条件编译消除未使用变量警告
|
||
|
||
```c
|
||
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 测试文件的手动注册
|
||
|
||
新测试文件需:
|
||
1. 添加到 `test/` 目录
|
||
2. 修改 `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 构建命令
|
||
|
||
```bat
|
||
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` | 已知硬件/软件陷阱及修复 |
|