构建测试

This commit is contained in:
2026-08-22 19:53:24 +08:00
parent a7f367f6a3
commit fc88bc8752
15 changed files with 2050 additions and 1578 deletions

View File

@@ -63,7 +63,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
### 启用测试
编辑 `Drivers/BSP/CH395F/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏。
编辑 `test/ch395f_test.h`,取消注释对应 `ENABLE_PHASEx_TESTS` 宏。
---
@@ -73,7 +73,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
|---------|-----------|
| **类型** | 独立运行 |
| **耗时** | ~1 秒 |
| **入口** | `ch395f_phase1_tests()``main.c USER CODE BEGIN 2` |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 1 Tests Complete" 且 0 失败 |
### TC-NET-101: SPI 命令路径完整性
@@ -85,23 +85,24 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| **类型** | 功能测试 |
| **标题** | 验证所有 SPI 命令路径返回预期值 |
| **前置条件** | 1. CH395F 已上电SPI2 已初始化<br>2. 未启用其他阶段 |
| **测试步骤** | 1. `ch395f_check_exist()` → 验证回显测试字节按位取反<br>2. `ch395f_get_version()` → 验证版本 != 0xFF 且 != 0x00<br>3. 设 IP 192.168.1.100,回读验证<br>4. 设网关 192.168.1.1,回读验证<br>5. 设掩码 255.255.255.0,回读验证<br>6. `ch395f_get_glob_int_status()` → 验证返回 0x00<br>7. 打开→关闭 Socket 0 → 验证 `CMD_STATUS == CH395F_ERR_SUCCESS`<br>8. 设置 ARP 参数<br>9. 设置 TTL |
| **预期结果** | 全部 9 项打印 `OK`/`SUCCESS`/`0x00` |
| **通过标准** | 9/9 通过0 失败 |
| **覆盖原则** | DP-02 |
### TC-NET-102: 全局中断状态一致性
**测试步骤与判定标准**
| 字段 | |
|------|-----|
| **ID** | TC-NET-102 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 无事件时 `GET_GLOB_INT_STATUS_ALL` 返回 0 |
| **前置条件** | TC-NET-101 通过,芯片空闲 |
| **测试步骤** | 1. 读 `ch395f_get_glob_int_status_all()`<br>2. 读 `ch395f_get_glob_int_status()`1 字节版 |
| **预期结果** | 两者均为 0 |
| **通过标准** | 两次读取均为 0 |
| | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `ch395f_check_exist()`<br>SPI写: CMD=0x06, DATA=0x57<br>回读: ~0x57 = 0xA8 | 返回 `0x00``CH395F_ERR_SUCCESS`<br>= 回读字节 `0xA8` 匹配 `~0x57` | 返回 `0xFF``CH395F_STATUS_NOT_DETECTED`<br>= 回读字节 ≠ `0xA8`,回显取反不匹配 |
| 2 | `ch395f_get_version()` | 版本值 == `0x4A`CH395F 芯片版本) | 版本值 ≠ `0x4A`(芯片不匹配或未响应) |
| 3 | `ch395f_set_ip_addr(192.168.1.100)``ch395f_get_ip_inf()` 回读 IP | `ip_info[0..3] == 192.168.1.100` | 任意字节不匹配(写入/回读不一致) |
| 4 | `ch395f_set_gwip_addr(192.168.1.1)``ch395f_get_ip_inf()` 回读 GW | `ip_info[4..7] == 192.168.1.1` | 任意字节不匹配 |
| 5 | `ch395f_set_mask_addr(255.255.255.0)``ch395f_get_ip_inf()` 回读 MASK | `ip_info[8..11] == 255.255.255.0` | 任意字节不匹配 |
| 6 | `ch395f_get_glob_int_status_all()`16 位) | 返回值 == `0x0000`(无待处理中断) | 返回值 ≠ `0x0000`(有残留中断未清除 |
| 7 | `ch395f_open_socket(0)``ch395f_close_socket(0)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00``CH395F_ERR_SUCCESS` | `CMD_STATUS``0x00`Socket 操作失败) |
| 8 | `ch395f_set_arp(10, 5)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS``0x00` |
| 9 | `ch395f_set_ttl(0, 64)``ch395f_get_cmd_status()` | `CMD_STATUS == 0x00` | `CMD_STATUS``0x00` |
**通过标准**9/9 通过0 失败。任一失败 → 阶段标记 FAIL后续阶段不执行
**覆盖原则**DP-02
---
@@ -111,7 +112,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
|---------|-----------|
| **类型** | 需 PC 配合 |
| **耗时** | ~60 秒 |
| **入口** | `ch395f_phase2_tests()``main.c USER CODE BEGIN 2` |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 2 Tests Complete" 且 0 失败 |
### TC-NET-201: TCP Client 连接与数据交换
@@ -123,8 +124,18 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
| **类型** | 功能测试 |
| **标题** | CH395F TCP Client → PC TCP Server64 字节收发 |
| **前置条件** | PC: `python ch395f_socket_test.py tcp_server --port 8081` |
| **测试步骤** | 1. CH395F 开 Socket 0配置 TCP<br>2. 连接 PC:8081<br>3. 发 64 字节 → 收回显 → 比较 |
| **通过标准** | 64 字节收发一致,无超时 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | CH395F open Socket 0 → 配置 TCP 协议 | `ch395f_open_socket(0)` 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`Socket 硬件异常或 SPI 通信失败) |
| 2 | 设置目标 IP 为 PC 地址(`192.168.1.2`),端口 `8081` → 发起 TCP 连接 | `ch395f_tcp_connect(0)` 返回 `CH395F_ERR_SUCCESS``0x00` | 返回值非 `0x00`(连接超时、目标不可达或协议配置错误) |
| 3 | 发送 64 字节递增模式数据(`0x00, 0x01, ..., 0x3F` | `net_send()` 返回值 == `64`(全部发出) | 返回值 < `0`(发送失败)或返回值 ≠ `64`(未完全发出) |
| 4 | 阻塞等待接收回显,超时 5 秒 | `net_recv()` 返回值 == `64`(收到全部回显) | 返回值 < `0`(超时/断开)或返回值 ≠ `64`(数据不完整) |
| 5 | 逐字节比较收发数据 | 所有 64 字节完全一致 | 任意字节不一致(回显错误或数据错位) |
**通过标准**5/5 通过0 失败。任一失败 → 阶段标记 FAIL后续阶段不执行
### TC-NET-202: TCP Client 关闭重连 ×3 轮
@@ -159,7 +170,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
|---------|-----------|
| **类型** | 需 PC 配合 |
| **耗时** | ~30 秒 |
| **入口** | `ch395f_phase3_tests()``main.c USER CODE BEGIN 2` |
| **入口** | `ch395fTestTask` 启动后自动运行 |
| **出口** | 串口 "Phase 3 Tests Complete" 且 0 失败 |
### TC-NET-301: UDP 回显 — HELLO + PING + 大包 ×30
@@ -196,7 +207,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
|---------|-----------|
| **类型** | PC 配合netTask 运行 |
| **耗时** | 持续运行 |
| **入口** | `ENABLE_PHASE4_TESTS`netTask 中循环调用 `ch395f_phase4_tests()` |
| **入口** | `ENABLE_PHASE4_TESTS``ch395fTestTask` TCP 回显循环中运行 |
| **出口** | 用户终止 |
### TC-NET-401: Socket 0 单连接生命周期
@@ -310,7 +321,7 @@ MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
### 实现说明
`ch395f_phase5_tests()` 状态机:
`phase5_run()` 状态机`ch395fTestTask` 内)
```
IDLE → (连接建立) → SENDING(分块发) → RECVING(分块收+验证) → DONE → IDLE

View File

@@ -0,0 +1,244 @@
# 嵌入式测试开发标准流程(基于 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 == 0xA8CH395F_ERR_SUCCESS = 0x00
// 失败:回复 != 0xA8CH395F_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_readynetTask 初始化完成)
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 实现)
→ @build0 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` | 已知硬件/软件陷阱及修复 |