# 测试开发标准流程(STM32F4-Base) > **定位**:本项目测试开发的总流程,覆盖"规划 → 板端套件 → 主机侧工具 → 验证门禁 → 提交沉淀"全链路,并给出未来测试设计的约束。 > 各环节的具体操作见下表对应文档,本文只讲流程、门禁与约定,不重复其内容。 **关联文档** | 文档 | 内容 | |---|---| | `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节(命令 / 标记 / 失败模式) | | `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板、命名规则(TM / TC / DP) | | `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试 / 采集工具与陷阱(Python 串口助手) | | `docs/测试说明/测试工具/串口助手使用指南.md` | 串口助手命令用法 / 工作流 / 排障 | | `docs/UI设计/UI_自动化测试流程.md` | UI 模拟器回归(ui-verify)细节 | | `docs/代码规范/pre-commit使用说明.md` | pre-commit 门禁配置与范围 | | `docs/环境与部署/项目部署环境要求.md` | 构建 / 测试工具链环境 | | `docs/CI/README.md` | **远程 CI(Gitea Actions)唯一入口**:服务端 / runner 部署与保活 / workflow / 排障(仅本机一台 runner,日常开发不必读) | | `docs/测试说明/<模块>_Test_Guide.md` | 各模块测试规范(CH395F / GD5F / SD2506 / FTP) | --- ## 1. 测试分层与门禁总览 测试体系分四层,门禁由便宜到昂贵依次布防,改动须过对应门禁方可提交: | 名称 | 载体 | 抓住什么 | |---|---|---| | 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ `/keil-build` | 编码规范、静态缺陷、任何编译警告 | | **主机侧纯逻辑单测** | `test/host/<模块>/run_unit_test.py` + MSVC 离线编译真实源码(`#include` 纳入)+ 平台桩 | **纯逻辑模块的边界穷举**(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 | | 板端测试套件 | testTask + `test/board/test_config.h` 套件 | 驱动 / 协议功能(串口 `[PASS]/[FAIL]` + `=== ... PASSED` 汇总) | | 主机侧回归 | `/serial-boot-test` 串口日志标记 + LVGL 模拟器像素 / 池水位回归 | 启动序列、UI 渲染、LVGL 内存池水位 | | 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 | > **主机侧纯逻辑单测与主机侧回归的区别**:主机侧回归的模拟器回归依赖 SDL / LVGL 与图形输出;主机侧纯逻辑单测是**纯计算**单测 > (无窗口、无硬件、秒级),专测"判定逻辑"。目前仅看门狗模块使用,见 > [`看门狗单元测试说明.md`](../单元测试/看门狗单元测试说明.md)。适格对象:算法 / 状态机 / 协议编解码 / > 参数校验等**无副作用**的模块。 > > **CI**:**静态门禁 + 主机侧纯逻辑单测已上 CI**(Gitea Actions,`.gitea/workflows/ci.yml`,2026-10-01); > 板端测试套件/主机侧回归/人工目检需硬件,只在本地。本地等价命令 `scripts/ci_local.py`。详见 §9。 ### 1.1 门禁决策表(改动类型 → 必跑门禁) | 改动类型 | 必跑门禁 | |---|---| | 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 `/keil-build` | | App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 `/serial-boot-test`(模块标记) | | **有主机侧纯逻辑单测的模块(如看门狗)** | G0(含该模块单测钩子,自动)+ G1 `/keil-build`(+ 台架项另按需 G2) | | UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 | | UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),**先于提交** | | `test/` 套件或 PC 端工具代码 | G1 + G2(套件标题标记 / 工具自测) | | 仅文档 | G0 | G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 `UI_自动化测试流程.md`。 --- ## 2. 测试规划(文档先行) ### 2.1 何时必须写测试文档 新驱动 / 新模块 / 新功能 / 新协议 / 新接口:动手写代码前先立 `docs/测试说明/<模块>_Test_Guide.md`,结构遵循 `测试文档模板规范.md`(概述含设计原则覆盖表 → 阶段元信息表 → 测试用例表 → 脚本参考 → 已知陷阱 → 附录矩阵)。 ### 2.2 命名(与模板规范一致) | 对象 | 格式 | 现用示例 | |---|---|---| | 阶段 | 直接写中文「阶段 N」(不再发号) | 阶段 6 | | 测试用例 | `TC-{MOD}-{NNN}` | `TC-NET-601`(CH395F)、`TC-GD5F-205`、`TC-STORAGE-03`、`TC-RTC-002` | | 设计原则 | 写语义短语(不再发号) | 单连接 socket | 模块缩写 `{MOD}` 在新模块测试文档立文档时确定(2~4 个大写字母);现有:`NET`=CH395F 网络、`GD5F`、`STO`=存储、`RTC`、`ADC`、`RS485`。 ### 2.3 三条铁律 1. **先写判定标准,再写测试代码** —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。 2. **判定标准必须可机器判定** —— 必须能落到:串口日志标记(→ `run_boot_test.py --required`)、返回值 / 寄存器值(→ `TEST_CHECK`)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 人工目检人工层。 3. **每条设计原则必须有对应用例** —— 覆盖表中每个 DP-xx 至少一个 TC;新功能设计时先定 DP,再设计 TC。 --- ## 3. 板端测试套件(test/) ### 3.1 统一入口与套件互斥 所有测试套件经唯一入口调度:**testTask**(`test/board/test_config.c` 的 `start_test_task()`),按 `test/board/test_config.h` 选中的 `TEST_SUITE_*` 分发到套件运行函数: ```mermaid flowchart TD T["testTask: start_test_task()(test/board/test_config.c)"] -->|TEST_SUITE_CH395F| A["ch395f_test_run()
硬件层 1~5 / NET 层 6~11"] T -->|TEST_SUITE_STORAGE| B["storage_test_run()
FTL + FatFS"] T -->|TEST_SUITE_GD5F| C["gd5f_test_run()
10 阶段"] T -->|TEST_SUITE_RTC| D["sd2506_test_run()
2 个阶段组"] T -->|全部注释=产品构建| E["空闲(仅打印 suite=NONE)"] ``` - **套件互斥**:一次构建最多启用一个 `TEST_SUITE_*`,否则 `#error`(编译期门禁);全部注释 = 正常产品构建。 - **测试固件不得静默提交**:`TEST_SUITE_*` 开关受 pre-commit 钩子 `test-suite-switch` 门禁(默认要求全部注释)。跑完套件回归**务必改回产品构建**再提交;确需提交测试构建时用 `ALLOW_TEST_BUILD=1` 显式放行(会打 `[WARN]` 横幅)。见 §7.1。 - **main.c / freertos.c 不含任何测试逻辑** —— 测试代码只能出现在 `test/`。 | 套件 | 宏 | 文件 | 阶段组织 | 备注 | |---|---|---|---|---| | CH395F | `TEST_SUITE_CH395F` | `ch395f_test.h/.c` + `net_test_task.c` | 硬件层 1~5 / NET 层 6~11 | 两层互斥(`#error`);阶段 2/3/4/5 与 6/7/8/10/11 需 PC 端配合 | | STORAGE | `TEST_SUITE_STORAGE` | `storage_test.h` + `storage_test_task.c` | FTL + FatFS 集成(TC-STORAGE-01~04) | 恢复测试(重建 BBT)默认关闭 | | GD5F | `TEST_SUITE_GD5F` | `gd5f_test.h` + `gd5f_test_task.c` | 10 阶段(选中时自动全开) | NAND 驱动全覆盖 | | RTC | `TEST_SUITE_RTC` | `sd2506_test.h` + `sd2506_test_task.c` | 阶段 1 基本 / 阶段 2 API(默认全开) | SD2506 RTC | ### 3.2 套件文件模式 每个套件两个文件,模式一致(参照 `ch395f_test.h` / `storage_test.h`): - `xxx_test.h` —— 阶段使能开关(取消注释启用)+ 本套件**独立统计**结构体 + `XXX_TEST_CHECK` / `XXX_TEST_REPORT` 宏 + `xxx_test_run()` 声明; - `xxx_test_task.c` —— `xxx_test_run()` 入口 + 每阶段一个 `static phaseN_run()`。 断言宏(语义统一,统计变量按套件独立,防跨套件串数): **内核集中在 `test_common.h`**(2026-10-01 抽取)——4 套件的宏此前逐字重复,改一处要改四处: ```c /* test/board/test_common.h */ typedef struct { uint16_t total, passed, failed; } test_stats_t; #define TEST_CHECK_IMPL(stats, cond, fmt, ...) do { \ (stats).total++; \ if (cond) { (stats).passed++; DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); } \ else { (stats).failed++; DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); } \ } while (0) #define TEST_REPORT_IMPL(stats, name) do { \ DBG_INFO("=== %s: %u/%u PASSED (failed=%u) ===", name, ...); \ } while (0) ``` 各套件**用一行宏把它绑定到自己的统计变量**(保持 `g_xxx_stats` 独立): ```c /* 套件头文件内(如 gd5f_test.h) */ #include "test_common.h" typedef test_stats_t gd5f_test_stats_t; extern gd5f_test_stats_t g_gd5f_test_stats; #define GD5F_TEST_CHECK(cond, fmt, ...) \ TEST_CHECK_IMPL(g_gd5f_test_stats, cond, fmt, ##__VA_ARGS__) #define GD5F_TEST_REPORT(name) TEST_REPORT_IMPL(g_gd5f_test_stats, name) ``` > ⚠️ **`test_common.h` 刻意不 include `dbg_log.h`**:`dbg_log.h` 在未定义 `DBG_TAG` 时给出 > 默认值 `""`,而本头文件被套件头在**很靠前**的位置包含——若在此处拉入,会把空 `DBG_TAG` > 冻结,导致 `.c` 随后 `#define DBG_TAG "[TEST_TASK]"` 触发 ARMCC `#47-D: incompatible > redefinition`(实测 8 条警告)。宏只在**使用点**展开(使用点在已按约定先定义 > `DBG_TAG` 的 `.c` 内),故无需在此引入。 每 Phase 用标准注释头(与文档用例一一对应): ```c /* * Phase N — 功能说明 * 测试目的 / 前置条件 / 通过 / 失败(四行,与 Test_Guide 文档一致) */ static void phaseN_run(void) { DBG_INFO("=== XXX Phase N Tests ==="); /* ---- TC-XXX-NNN: 用例标题 ---- */ { // 原理说明;通过:精确条件;失败:精确条件 ret = some_api(); DBG_INFO(" expect: ..."); DBG_INFO(" actual: ..."); XXX_TEST_CHECK(ret == EXPECTED, "description"); } XXX_TEST_REPORT("Phase N"); } ``` **套件运行结束行的 `=== <名称> Tests: N/N PASSED (failed=0) ===` 即该套件 `/serial-boot-test` 回归的默认标记**(各套件确切字符串见对应模块 Test_Guide,如 `=== Storage Tests:` / `=== GD5F2GQ5UE Tests:`)。 ### 3.3 编码约定 | # | 约定 | 说明 | |---|---|---| | 1 | 判定点三行输出 | `expect:` / `actual:` / `[PASS]/[FAIL]`,一眼看出"期望什么、拿了什么、过没过" | | 2 | 硬编码值加注释 | 协议字节 / 寄存器 / 阈值必须注释含义与来源(手册页 / 协议条款) | | 3 | 日志字符串纯 ASCII | ARMCC v5 不识别 UTF-8 多字节字符(`#870-D` 警告);注释可中文,`DBG_*` 字符串不可 | | 4 | 大缓冲用 static | 大缓冲(64KB 级)`static uint8_t s_rx_buf[N]`,不放任务栈 | | 5 | 条件编译包裹变量 | 仅特定阶段使用的变量 / 函数用 `#if` 包裹,消除 `#177-D` 未使用变量警告 | | 6 | 就绪等待用就绪屏障 | 任务间时序依赖 `SYS_EVT_*`(`osEventFlagsWait`),**禁止魔法延时** | > 注意:`test/` 在 pre-commit 检查范围之外(工具代码豁免)——上表约定属**自律**要求,但 0 错 0 警的构建红线对测试代码同样生效。 ### 3.4 测试构建下的 netTask(2026-09-15 已修复,未来测试设计必读) 代码事实(2026-09-15 修复后): 1. `netTask` = **基础线程**:定义于 `.ioc` → `Src/freertos.c` 的 **CubeMX 生成区无条件创建**(全构建唯一实例)。产品 / NET 层测试模式下正常运行:等存储就绪(≤10s)→ `net_stack_bring_up()`(CH395F 初始化)→ 置 `SYS_EVT_NET_READY` → `net_poll()` 轮询循环。 2. ~~USER CODE 区在 `ENABLE_NET_LAYER_TESTS` 下另建一个 netTask(双 netTask 并存)~~ → **2026-09-15 已删除**:NET 层测试构建不再重复创建。 3. **CH395F 硬件层模式下 `net_task_func` 入口自挂起**(2026-09-15 守卫,`ENABLE_HW_LAYER_TESTS`):不 bring_up、不轮询、不广播 NET_READY;testTask 自行 `net_stack_bring_up()` 后独占 CH395F 跑各阶段。任务仍被创建(基础线程策略:恒存在、该模式"不运行"),占 4KB 栈——彻底方案(`.ioc` 移除 + 条件创建)经评估未采用:网络线程是基础线程,仅测试模式"不运行"。 由此产生的设计约束: - **硬件层模式**:netTask 已挂起,**无人**消费/清除芯片全局中断标志——"中断状态寄存器全程 0x0000"类断言在该模式下**安全**(测试入口的预读清除仍须保留,用于清除 `net_stack_bring_up` 自身强制 PHY 重协商锁存的 `PHY_CHANGE` 残留)。 - **NET 层模式 / 产品构建**:netTask 恒存在且在轮询(全局中断由它消费清除)——新增测试用例**不得**断言"无人动芯片/中断"。 - 硬件层模式下挂起的 netTask 仍占 4KB 栈,为基础线程策略的接受成本(彻底方案可回收,暂无计划)。 ### 3.5 新测试文件 / 新套件 checklist 1. `test/` 下新建 `xxx_test.h` / `xxx_test_task.c`(按 §3.2 模式); 2. `test/board/test_config.h`:补 `TEST_SUITE_XXX` 注释开关 + 互斥 `#error` 加项 + 分发分支(include + `TEST_SUITE_NAME`); 3. `test/board/test_config.c`:`start_test_task()` 加分发分支; 4. **双端注册核查**(`new-source-file` skill):新 `.c` 必须加入 `MDK-ARM/STM32F407-Demo.uvprojx`;test/ 代码仅 MCU 端编译,**不**登记模拟器 CMakeLists(UI / App 共用代码才需双端); 5. `/keil-build` → 0 错 0 警; 6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板); 7. `/serial-boot-test` 以套件标题行为 `-Required` 标记跑通回归。 ### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加) 套件互斥(编译期 `#error`)→ 跑 4 个套件的回归本来要手工循环 4 轮 (改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本编排成一条命令: ```powershell & '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件 & '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F & '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划 & '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录 ``` **核心保障:`try/finally` 无条件还原 `test_config.h`**(含异常 / `KeyboardInterrupt` 路径)——跑完忘还原 = **测试固件进库**。这与提交期门禁 `test-suite-switch`(§7.1) 是**两条独立防线**:前者是流程自动化、后者是门禁兜底。 > ⚠️ **完整回归不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起, > 且破坏性套件会改设备持久状态。仅回归窗口手工使用。 #### 3.6.1 `--check-build`:只编译不烧录(2026-10-02 加) **解决的问题**:测试套件的**构建可编译性没有独立门禁**——产品构建 `/keil-build` 天天跑, 但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件 7 个配置实例)。 ```powershell & '.venv\Scripts\python.exe' test/run_all_suites.py --check-build ``` | 项 | 说明 | |---|---| | 做什么 | 逐套件:改 `test_config.h` 宏 → `/keil-build` → 记 PASS/FAIL;结束后无条件还原 | | **不需要** | MCU / 串口 / ST-LINK(**纯离机**,可在 CI 或任何有 Keil 的机器上跑) | | 判定 | `/keil-build` 退出码 0 = PASS;非 0(含 `L6406E` 链接失败)= FAIL | | RAM 溢出 | "编译过但链接失败"这类问题会**自然暴露**为 `L6406E`——这正是想要的 | | 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 | > **2026-10-02 实测**:4 套件(CH395F / STORAGE / GD5F / RTC)**全部 `BUILD_OK: 0 errors, 0 warnings`** > ——即前述"CH395F 套件不可编译"问题**已修复**,现已纳入可自检范围。 > ⚠️ **隐藏陷阱:`.map` 必须复位为产品态**(2026-10-02 实测踩到)。脚本跑完最后一轮留下的 > `STM32F407-Demo.map` 是**测试套件**的体积(RTC 套件下 ER_IROM1 达 266 KB), > 会被 `mem-numbers` 钩子判为"文档快照与 `.map` 不一致"→ **下次提交直接 FAIL**。 > 故脚本在还原 `test_config.h` **之后**追加一次产品构建(`[..] 复位构建产物为产品态`)—— > **仅还原源码不够,必须再编一次**。同理适用于完整回归模式。 **建议用法**:改动 `test/board/` 下任何测试代码、或改 `test_config.h` 后,跑一次 `--check-build` 再提交;比逐个手工改宏构建省事,且不会忘记还原(含 `.map` 复位)。 --- ## 4. 主机侧测试工具 ### 4.1 工具清单 | 工具 | 位置 | 作用 | |---|---|---| | Python 串口助手(serial_tool) | `.dsh/skills/serial-assistant/scripts/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle | | `.dsh/skills/serial-boot-test/scripts/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 | | **`test/run_all_suites.py`** | `test/` | **多套件回归编排** + **`--check-build` 只编译不烧录**(纯离机,验证 4 套件可编译性);见 §3.6 | | `.dsh/skills/serial-boot-test/scripts/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验 | | **`test/host/common/unit_test_harness.py`** | `test/host/common/` | **主机侧纯逻辑单测通用单测 harness**:工具链探测 / `INCLUDE`-`LIB` 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置 | | **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **主机侧纯逻辑单测**:声明 `TestBuild` 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 | | **`test/host/sys_clock/run_unit_test.py`** | `test/host/sys_clock/` | **主机侧纯逻辑单测**:穷举 `sys_clock` 时间换算 / 星期 / 惰性再基准化 / RTC 故障降级(100 断言,首批主机侧单测) | | **`test/host/cfg_file/run_unit_test.py`** | `test/host/cfg_file/` | **主机侧纯逻辑单测**:INI 解析 / 值编解码 / 段注册 / load-save-poll 契约,内存虚拟 FS 驱动(139 断言,首批主机侧单测) | | **`test/host/nand_port/run_unit_test.py`** | `test/host/nand_port/` | **主机侧纯逻辑单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 dhara,以 **RAM 假芯片**驱动,149 断言钉死 is_free 整页扫描与六个入口的越界守卫 | | **`scripts/ci_local.py`** | `scripts/` | **本地跑与 CI 等价的门禁**(静态门禁 + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;`.map` 自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。见 §9.3 | | `.serialtest/config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 | | `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) | | `simulator/tests/*.py`(4 个) | `simulator/tests/` | LVGL 模拟器回归脚本(见 §5) | ### 4.2 主机侧纯逻辑单测(纯逻辑,无硬件) **适用对象**:**无副作用**的纯逻辑模块——算法、状态机、协议编解码、参数校验等 (例:看门狗的位图判定 / 掩码 / 计数阈值 / 整数回绕)。 **做法**(以看门狗为范式,见 [`看门狗单元测试说明.md`](../单元测试/看门狗单元测试说明.md)): 1. 模块的**硬件访问**收敛到薄平台层(`__weak` 钩子或后端函数)——参照 `App/cfg/cfg_file_store.h` 的"共享核心 + 平台后端"; 2. 测试单元 `#include` **生产源文件**(保证测的是真代码,非副本),用桩替换平台层; 3. 写模块侧运行器:**只声明** `TestBuild`(源码 / 桩目录 / 汇总行标记)+ 调 `test/host/common/unit_test_harness.py`(工具链探测与编译运行细节在 harness 内, 见 [`../../../test/host/common/README.md`](../../../test/host/common/README.md)); 4. 挂 pre-commit 钩子,被改动时自动跑(**只 commit 不烧录**,秒级)。 **环境要求**(详见 [`../../环境与部署/项目部署环境要求.md`](../../环境与部署/项目部署环境要求.md) §2.3 / §2.7): | 项 | 要求 | 说明 | |---|---|---| | MSVC `cl.exe` | **装了 VS(2019/2022/2026)即有** | 与 LVGL 模拟器**共用同一套 VS**,无需另装;脚本按本机 VS/SDK 目录自动拼 `INCLUDE`/`LIB` 后直接调 `cl.exe` | | Windows SDK | 随 VS 勾选"使用 C++ 的桌面开发"+ SDK | `cl.exe` 编 `stdio.h` 等标准头从这里取 | | Python | 仓库 `.venv` 或任意 python3 | 运行器**纯标准库**,无需第三方依赖 | | 硬件 | **无** | 不需要开发板 / 串口 / ST-LINK | | 缺失时 | 运行器报环境错误(**退出码 2**)→ pre-commit 钩子**拦截提交** | 未装 VS 的机器改动被测文件时会被拦,须先装 VS | > ⚠ **不调 `vcvars64.bat`**:它内部调用 `reg.exe` 探测环境,部分受限机器(安全策略禁 `reg.exe`) > 会失败 → 无 `INCLUDE` → `fatal error C1083: 无法打开包括文件 "stdio.h"`。 > 运行器因此**直接拼 `INCLUDE`/`LIB` 环境变量 + 绝对路径调 `cl.exe`**,无副作用、可重复。 **约束**: - 平台层的抽取必须**零行为变化、零额外开销**(MCU 端默认实现即原逻辑;`/keil-build` 须 0/0); - 测试专用钩子(如静态态复位)用 `#ifdef <模块>_UNIT_TEST` 守卫,MCU 构建不编入(零符号); - 单测**不能替代**板端验证:真机寄存器 / 复位原因 / 时序仍须走板端测试套件 / 主机侧回归台架闭环。 #### 4.2.1 主机侧纯逻辑单测:适格模块台账(2026-10-02 加) **问题**:主机侧纯逻辑单测起步阶段只有"看门狗"一个先例,**下一个该测谁**靠临时判断,易遗漏高价值模块。 **判据**(是否适格 主机侧纯逻辑单测):模块的核心逻辑是否**无副作用**且**与硬件解耦**—— 满足"纯逻辑 + 已抽象平台层(或依赖全是外部符号/宏)"即可主机侧穷举。 | 模块 | 规模 | 状态 | 平台层形态 | 备注 | |---|---|---|---|---| | `App/util/task_watchdog.c` | — | ✅ 已覆盖(39 断言) | 内联 `__weak` 钩子 | 范式来源;`#ifdef TASK_WATCHDOG_UNIT_TEST` 复位钩子 | | `App/util/sys_clock.c` | 479 行 | ✅ 已覆盖(100 断言,2026-10-02 首批) | 外部符号(`HAL_GetTick`/`sd2506_*`)+ 宏 | **零侵入**(无需 `#ifdef`);首测即抓出 Zeller 星期缺陷 | | `App/cfg/cfg_file.c` | 1250 行 | ✅ 已覆盖(139 断言,2026-10-02 首批) | 后端抽象(`cfg_file_store.h`) | 内存虚拟 FS 桩;**首测即抓出外科手术保存死码 P0**(见 §5) | | `App/log/sys_log_mcu.c` | — | ⬜ 候选 | 记录编解码(定长 64B) | 编解码纯逻辑;写入端依赖 FatFS + 队列,测编解码即可 | | `App/util/time_sync.c` | — | ⬜ 候选 | 协议帧编解码 | 报文组装/解析纯逻辑;网络收发不测 | | `Drivers/BSP/NETCFG/net_cfg_legacy.c` | — | ⛔ 已删除(2026-10-10) | — | 旧档 NCFG 迁移整体移除(旧档 `/cfg/netcfg.bin` 不读、不删、不迁移),测试候选随之撤销 | | `App/protocol/iec104/*`(104 协议栈) | — | ✅ 已覆盖(82 断言,2026-10-12 IEC 104 上行) | 数据源 vtable + tick 注入 + 发送回调注入 | APCI 重组/总召分片/COV/k-w-t1~t3/两段式全离线穷举;`iec104-unit-test` 钩子 | | `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c`(dhara 移植层) | 573 行 | ✅ 已覆盖(149 断言,2026-10-15 起离线钉死 dhara 移植层两条硬约束) | 11 个 SPI 原语(`gd5f2gq5ue.h`)→ **RAM 假芯片** | 移植层是纯地址算术 + 边界判定,换掉芯片即可离线跑;钉死 is_free 整页扫描与六入口越界守卫,并内联旧算法做对照证明断言有牙 | | `App/util/rs485.c` | — | ⏸ 待功能实现 | — | 上游 RS-485 采集链路;当前为空桩 | | `Drivers/BSP/TPAFE5160/*.c` | — | ❌ 不适格 | — | 寄存器序列为主,需真机时序 | | 其余驱动(CH395F/GD5F2GQ5UE 芯片层/SD2506) | — | ❌ 不适格 | — | 需硬件时序 / SPI / I2C 总线。⚠ **例外**:GD5F 之上的 `nand_ftl.c` 移植层**适格**(见上一行)——"驱动不适格"不等于"驱动之上的胶水层不适格" | | `App/util/crc.c` · `ringbuf.c` | 120/154 行 | ⛔ 已删除(2026-10-02) | — | 孤儿文件,零引用,已 `git rm` | **使用方式**:新增/改动上述模块前,按表判断是否需补 主机侧纯逻辑单测;`⬜ 候选` 表示"适格但未做", 在性价比允许时应排入(判据:边界密集、零硬件、单次真机回归 30s~2min vs 单测 2~5s)。 > ⭐ **两条实证**:`sys_clock` 与 `cfg_file` **首次落地即各抓出一处 > 生产缺陷**(Zeller 星期公式错误 / 外科手术保存死码)。这印证了本台账的性价比判断—— > 高价值纯逻辑模块的 主机侧纯逻辑单测覆盖,是**投入最小、产出最大**的测试改进方向。 ### 4.3 新工具约定 - **test/ 自动化脚本为纯 Python 标准库**(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;`requirements.txt` 仍须纯 ASCII——pip 按 GBK 代码页解码); - **Python 统一用仓库 `.venv`**;第三方依赖必须进 `requirements.txt`(纯 ASCII); - 落位:板端交互工具 → `test/`;模拟器回归 → `simulator/tests/`; - 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供 `/serial-boot-test` 闭环复用。 --- ## 5. UI 测试门禁(ui-verify) **触发条件**:任何 `Drivers/BSP/UI/` 下改动、字体(`ui_font_cn_12.c` / `ui_cn_chars.txt`)、菜单 / 内容页。 ### 5.1 五步门禁(顺序执行,全过) ```powershell cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警告) .venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 1) 菜单 8 状态像素回归 + 池基线 .venv\Scripts\python.exe simulator/tests/check_content_pages.py # 2) 内容页黄金参考 + 往返池水位 & '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 3) Keil 构建(UI 源码双端共用,改一端必过另一端) .venv\Scripts\python.exe scripts/check_coding_standard.py # 4) 代码规范机械检查 ``` 远显推流通道另按需跑 `simulator/tests/check_remo_sink.py`。 ### 5.2 三层断言(两个回归脚本内置) | 层 | 载体 | 抓住什么 | |---|---|---| | 文本层 | `check_content_pages.py` 字形模板匹配(310 条、覆盖当时 37 页,解析字体 cmap / glyph_dsc / bitmap) | 缺字显示空白 / 标签写错等**静默失败**(不依赖 `ref/`,先于像素比对运行、失败即中止防误刷参考) | | 像素层 | 内容页黄金参考 66 张(64 页)+ 菜单 8 状态,与黄金参考逐字节比对 | 几何 / 样式 / 光标反显 / 整体外观 | | 池水位层 | `MENU_POOL_BASELINE=5368 B` / `POOL_AFTER_ROUNDTRIP=5448 B` | LVGL 内存池泄漏 / 结构性漂移(量的是 `used`,与 `LV_MEM_SIZE` 总大小无关) | ### 5.3 黄金参考与字体纪律 - `simulator/tests/ref/*.bin`(像素黄金参考)**入库**(2026-10-22 起,新克隆即可跑回归;跨独立构建目录逐字节可复现已实测);派生的 `.png` 目检图仍不入库; - `--update` 有**误刷保护**(先跑文本层断言,失败即中止)——**未有意改视觉而 FAIL = bug,修代码,禁止刷参考掩盖**;基线入库后刷参考会在 `git diff` 里显形,必须逐页过目并在提交正文说明; - 刷新参考后必须重跑 `regen_ref_png.py` 重生成目检 PNG(否则旧 PNG 看似乱码); - 门禁常量漂移:先确认新值合理(残留分解见 `菜单页面设计.md` §9.5)→ 更新常量 + 在 `UI_自动化测试流程.md` 正文写清当前常量值与它的实测来源; - **UI 新增中文文案提交前必须先跑 font-regen**(extract_cn → make_font → diff 检查):字符集 `ui_cn_chars.txt` 缺字则上机显示空白——**2026-10-15 起该约定已入门禁**(`font-charset` 钩子:源码在用字符 ∉ txt 或 txt ∉ 字体 cmap → 提交 FAIL,详见 `docs/门禁说明/font-charset.md`)。 --- ## 6. 自动化闭环(/serial-boot-test) ### 6.1 流程 ```mermaid flowchart TD A[代码改完] --> B["G1 /keil-build(0 错 0 警,否则修复重编)"] B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"] C --> D["@download 烧录(Reset & Run 触发重启)"] D --> E[提取日志 + verify_log 标记校验] E -->|exit 0| F["PASS → G3(UI 改动)→ 人工目检上机目检 → 提交"] E -->|exit 1 FAIL| G["[MISS]/[HIT] + 日志尾部定位 → 修代码"] G --> B E -->|exit 2/3 环境错误| H["按 自动化测试流程.md 失败模式表处理 → 重试"] ``` ### 6.2 判定标记 默认:`-Required "App init OK;Network Init;net init: OK"` / `-Forbidden "FAIL;ERROR;HardFault;assert"`。 | 回归场景 | `-Required` 定制 | |---|---| | 网络 | `App init OK;Network Init;net init: OK;PHY: 100M FULL` | | UI 初始化 | `UI port init: OK;UI menu init: OK;UI net sink: started` | | 开机全量(存储 / RTC / RS485 / ADC) | 默认 + `RS485 init: OK;NAND init:;ADC init:;SD2506 ID:` | | 测试套件构建 | 套件结尾汇总行(`=== Storage Tests:` / `=== GD5F2GQ5UE Tests:` 等,确切字符串见各模块 Test_Guide) | ### 6.2.1 套件汇总行校验(2026-10-01 加) > ⚠️ **纯 Required 子串匹配有盲区**:套件启动横幅命中即判 PASS——**套件是否跑到汇总行、 > 断言数是否为 0 完全不可见**。套件中途挂死/复位、或 Phase 宏全关(没编入任何断言)时, > 会被**误判为通过**。测试套件构建的回归**必须**加 `--require-suite-summary`。 ```powershell # 测试套件构建回归(要求套件跑到汇总行,且断言数 > 0) & '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py ` --required "=== Storage Tests:" --forbidden "ERROR;HardFault;[ERR]" ` --require-suite-summary "Storage Tests" ``` 校验语义(`verify_log.py`): | 情形 | 判定 | |---|---| | 汇总行存在、`N>0`、`failed=0`、`N==M` | PASS(打印 `[OK] suite summary ok`) | | 汇总行**缺失**(套件没跑到/挂死) | FAIL `[MISS]` | | `total == 0`(没编入任何断言) | FAIL `[FAIL] total is 0` | | `failed != 0` | FAIL `[FAIL] reports failures` | | `passed != total` | FAIL `[FAIL] inconsistent` | > 未传 `--require-suite-summary` 时行为与旧版**逐字一致**(向后兼容,产品构建回归不受影响)。 ### 6.3 三条铁律 1. **采集必须先于烧录武装** —— 开串口 + clear 在烧录前完成(daemon 常驻全量日志兜底,不会漏字节;`run_boot_test.py` 已保证顺序); 2. **端口以 `.serialtest/config.json` 为准** —— 每次连接前先读它; 3. **静默判据覆盖时间窗** —— `-SettleMs=8000`(覆盖 boot ~3-4s 自然静默间隙)、`-MaxWaitMs=30000`(整体超时兜底); --- ## 7. 提交门禁与沉淀 ### 7.1 提交门禁(G0,pre-commit) 提交被钩子门禁,任一失败即拦截: | 钩子 | 内容 | |---|---| | trailing-whitespace / end-of-file-fixer / check-added-large-files / check-merge-conflict / check-yaml | 官方空白 / 文件尾 / 大文件 / 冲突 / YAML 检查 | | gitlint | commit message 标题 ≤100 字符 | | cppcheck | 静态分析(warning / performance / portability / style,含 suppress 清单) | | no-mojibake | 禁止 U+FFFD 乱码字节(保护中文注释) | | coding-standard | 代码规范 V1.0 机械检查(条款号 `CODE-RULE-nn`,规则表在 `scripts/check_coding_standard.py`) | | **test-suite-switch** | **测试套件开关=产品构建**(`test/board/test_config.h` 的 `TEST_SUITE_*` 须全注释;跑套件回归确需提交测试构建时用 `--allow-test-build` 显式放行) | 范围:仅项目自有代码受检;第三方与生成物(`Lib/`、`Middlewares/`、CMSIS/HAL、`lftpd`、`dhara`、`MDK-ARM/`、`test/`、CubeMX 生成的 `Src/` `Inc/` 等)被排除(详见 `pre-commit使用说明.md`)。 ### 7.2 提交纪律 - **逻辑提交**:一笔提交只做一件事(代码 / 文档 / 工具分开); - **编码铁律**(最高优先级):含中文文件只用 Edit 工具精确替换修改;**禁止** PowerShell / cmd 整文件重写(U+FFFD 乱码不可逆);BOM 状态按原样保持(UI 相关源 / `lv_conf.h` 带 BOM,其余无 BOM); - **内存预算**:新增任务 / 缓冲 / 栈前必须先对照 RAM 红线(当前 SRAM 占用 95.4%、仅余 ~5.8KB,见 `docs/内存管理/`)。 ### 7.3 沉淀(必须) | 事件 | 动作 | |---|---| | 测试发现陷阱(硬件 / 协议 / 工具链 / 时序) | 写入 `docs/问题记录/<模块>_Trap_Records.md`:现象 / 根因 / 修复 / 规避 | | 代码变更影响判定标准 | 立即把模块 Test_Guide **正文**改到与代码一致(现状说了算,不再记流水) | | 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + 把新口径写进 `UI_自动化测试流程.md` 正文对应小节 | | 新增测试工具 / 新标记 | `自动化测试流程.md` 命令清单登记(§4.2) | | 栈 / 堆预算相关改动或基线重采 | 把 `/serial-boot-test` 的 boot_log 快照(`%TEMP%\serial-boot-test\boot_log_*.txt`)存入 `docs/测试说明/测试数据/boot_log_data/` 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 `[STACK]` 行) | > 文档与代码一体两面:**代码改了文档没同步 = 未完成**。 --- ## 8. 迭代节奏(完整闭环) ```mermaid flowchart LR A["文档:规划 + 判定标准
(Test_Guide)"] --> B["代码:套件 / 工具实现
+ 双端注册核查"] B --> C["G1 /keil-build
0 错 0 警"] C --> D["G2 /serial-boot-test
采集→烧录→校验"] D -->|PASS| E["G3 ui-verify
(UI 改动时)"] E --> F["人工目检上机目检"] F --> G["提交
G0 门禁 + 逻辑提交"] G --> H["沉淀:陷阱记录 /
文档同步 / 正文现状化"] D -->|FAIL| I["[MISS]/[HIT] 定位"] I --> B ``` 各模块测试迭代都走这个闭环:文档先行、门禁验收、沉淀回灌。 --- ## 9. CI 持续集成(Gitea Actions,2026-10-01 加) 远程仓库为 **Gitea 1.25.4**(`gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git`),支持 Gitea Actions (兼容 GitHub Actions 语法,workflow 置于 `.gitea/workflows/`)。落地文件 **`.gitea/workflows/ci.yml`**。 ### 9.1 覆盖范围:只做"不依赖硬件"的层 | 层 | 上 CI? | 载体 | 理由 | |---|---|---|---| | **静态门禁** | ✅ | `pre-commit run --all-files`(提交期 47 根快钩子)+「手动阶段门禁」步逐根 `--hook-stage manual` 跑 `cppcheck` / `test-cppcheck` / `test-coverage` | 纯 Python + cppcheck,零硬件 | | **主机侧纯逻辑单测** | ✅ | `test/host/**/run_unit_test.py`(MSVC 离线编译) | 纯标准库 + `cl.exe`,零硬件 | | 板端测试套件 | ⛔ | `test/board/` | 需真机 + 串口 + ST-LINK | | 主机侧回归 `/serial-boot-test` 闭环 | ⛔ | 烧录 + 串口采集 | 同上 | | 人工目检 | ⛔ | 上机屏幕 | 人的判断 | **明确不做的两件事(都是刻意为之)**: 1. **不跑 `/keil-build`(Keil UV4)**——需本机 Keil 授权,runner 上不具备。静态门禁的 cppcheck + 代码规范 已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(`AGENTS.md`)。 2. **`mem-numbers` 钩子按 `.map` 有无自适应跳过**——它的事实源是 `MDK-ARM/**/*.map` (Keil 构建产物,已 `.gitignore`),无 `/keil-build` 即无 `.map`。故 workflow 在**检测不到 `.map` 时自动跳过该钩子**(`SKIP=mem-numbers`,钩子显示 `Skipped` 而非 `Passed`—— 是"不适用",不是"通过");有 `.map` 的 runner(如自建带 Keil 的机器)则照常跑。 > 🔴 **门禁盲区(2026-10-02 记录)**:跳过 `mem-numbers` 意味着 **CI 不守 Flash/RAM 体积门禁**—— > 新增任务/栈/缓冲/代码导致的体积增长**不会在 CI 被拦**。故该跳过以 `::warning::` 明示 > (不是弱化的 notice)并写入 step-summary。**兜底必须靠本地**:改代码后 `/keil-build`(0/0) > → `python scripts/check_mem_numbers.py --scan`。详见 > [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §0.0。 > 🔴 **`gitlint` 属 `commit-msg` stage,`--all-files` 永远跑不到它(2026-10-11 审计)**: > `.pre-commit-config.yaml` 当时定义 **18** 个钩子,而 CI 日志只报 **17** 个(`passed=16 skipped=1`)—— > 缺的正是 gitlint。即"单行中文标题 ≤100 字符"这条门禁**本地有、CI 无**,且表现为**全绿** > 而非失败,所以长期无人发现。现由 CI 侧 **`scripts/ci_gitlint_range.ps1`**(PR 用 `base..head`、 > push 用 `event.before..sha`、跳过 merge 提交)与本地 `ci_local.py` 的 **提交信息门禁** 段分别逐条校验。 > ⚠️ 机制根因(`stages: [commit-msg]` 写在 **gitlint 上游** 的 `.pre-commit-hooks.yaml` 里, > 项目配置中 grep 不到)与完整排障口径见 > [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §7"钩子条数对不上"行。 ### 9.2 两个 job | job | 覆盖层 | 一句话职责 | |---|---|---| | `static-gates` | **静态门禁** | `pre-commit run --all-files`(编码规范 / 文档一致性 / 主机侧单测快归因钩子等 47 根)+「手动阶段门禁」步逐根 `--hook-stage manual` 跑 `cppcheck` / `test-cppcheck` / `test-coverage` + **提交信息门禁**(gitlint,见上方 🔴);无 `.map` 时自适应跳过 `mem-numbers` | | `host-unit-tests` | **主机侧纯逻辑单测** | 探测 MSVC 后遍历 `test/host/**/run_unit_test.py` 逐个跑并聚合断言数;**未装 MSVC 时降级为 WARNING 跳过**(不硬失败,但等于该层裸奔) | 触发:`push`(`paths-ignore: docs/** / **/*.md / .workbuddy-ai/**` → **纯文档改动不会触发 CI**) 与 `pull_request`,另留 `workflow_dispatch` 手动触发;同分支 `cancel-in-progress: true`。 两个 job 都写 step-summary + `::notice::` 汇总行,日志落 `ci-artifacts/*.log`。 > 📖 **步骤级细节不在本文**(探测链顺序、`GITHUB_OUTPUT` 跳过决策、`Tee` 落盘、产物存档、 > 内网拉不到 action 的处置):见 [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md) §5.5 / §5.2 / §5.3 / §5.4(依次为:两个 job 的步骤要点 / 触发条件 / 内网 action 拉取 / 产物与汇总)。 ### 9.3 本地等价:`scripts/ci_local.py` CI 只在 push / PR 时跑,且需 runner 就绪。**提交前自查、或 runner 还没配好时**,用同一套判定在本地跑: ```powershell & '.venv\Scripts\python.exe' scripts/ci_local.py # 静态门禁 + 提交信息门禁 + 主机侧纯逻辑单测(与 CI 等价) & '.venv\Scripts\python.exe' scripts/ci_local.py --static-only # 只跑静态门禁(含提交信息门禁) & '.venv\Scripts\python.exe' scripts/ci_local.py --host-unit-only # 只跑主机侧纯逻辑单测 & '.venv\Scripts\python.exe' scripts/ci_local.py --commits 20 # 提交信息校验最近 20 条 ``` - **默认段 = 静态段(47 根)+ 提交信息段 + manual 段 + 体积门禁 + 主机侧纯逻辑单测**:manual 段逐根 `--hook-stage manual` 跑 `cppcheck` / `test-cppcheck` / `test-coverage`(三根带 `stages: [manual]`, `git commit` 阶段不跑;一次只能给一个 id,多 id 实测报 `unrecognized arguments`); `--coverage` 旗标已并入 manual 段,保留只为兼容旧命令。 - **自动选用装了 pre-commit 的解释器**(优先级:`DTU_PRECOMMIT_PYTHON` 显式指定 → 当前解释器(重建 `.venv` 已自带 pre-commit)→ 仓库 `.venv` → PATH 的 `python` → **常见机器级安装位置**;显式值无效 → 硬报错并指明变量名,不静默回退); 仍不可用时退出码 2 并列出**全部已尝试路径**。 ⚠️ 该候选表与 CI 侧 **`scripts/ci_find_python.ps1`** 必须**同序同步**——2026-10-11 的 静态门禁全灭就是 workflow 单方面把候选收窄成"PATH python + 仓库 `.venv`"(runner 上两者都是死路)导致的。 - **提交信息门禁**:与 CI 的 `ci_gitlint_range.ps1` 同语义(`--all-files` 跑不到 commit-msg stage)。 - **`.map` 自适应与 CI 完全一致**:本地有 `.map` 就照常跑 `mem-numbers`,没有就跳过。 - 退出码:`0` 通过 / `1` 门禁或单测失败 / `2` 环境错误。 > ⚠️ **CI 不能替代本地门禁**:`.map` 类体积门禁、`/keil-build`、板端测试套件/主机侧回归台架项都只存在于本地。 > 提交前跑一遍 `ci_local.py` 仍是**强制**动作,见 §7.1(`pre-commit run --all-files` 只覆盖提交期 47 根, > 走不到 `commit-msg` 与 manual 阶段)。 ### 9.4 部署前置(需人工确认) CI 能不能跑起来,取决于 runner 机器上四件事是否就位:**服务端开关 / runner 注册与 labels / 看门狗常驻 / 机器级 `DTU_*` 工具变量**。清单与逐项部署步骤都在 [`../../CI/远程CI完整指南.md`](../../CI/远程CI完整指南.md):§0.2(前置清单)、§2(注册)、§2.4(**看门狗**)、§4.1.3(runner 以 SYSTEM 跑,必须写 MACHINE 级变量)。 注册细节:`--instance` 用回环、labels 必须精确匹配 `runs-on`;看门狗缺了它 Gitea 一重启 CI 就静默停摆(2026-10-11 实测 run 16~21 共 12 个 job 零日志)。 > ⚠️ 对测试流程只需记住一条:**runner 不在线时,`ci_local.py` 就是 静态门禁 / 主机侧纯逻辑单测的唯一保障**。 > "CI 显示绿"与"本地门禁通过"是两回事,见 §9.3 与 §7.1。 --- ## 附录 A:test/ 文件清单(现状) | 文件 | 作用 | |---|---| | `test_config.h` / `test_config.c` | 套件选择(互斥 `#error`)+ 统一调度入口 `start_test_task()` | | `test_common.h` | **板端测试公共断言内核**(`test_stats_t` + `TEST_CHECK_IMPL`/`TEST_REPORT_IMPL`) | | `ch395f_test.h` / `ch395f_test_task.c` | CH395F 套件:阶段开关、断言宏、Phase 1~5 实现 | | `net_test_task.h` / `net_test_task.c` | CH395F 套件 NET 层(6~11):TCP 监听循环 / Client 主动连接测试 | | `storage_test.h` / `storage_test_task.c` | STORAGE 套件:FTL + FatFS 集成(TC-STORAGE-01~04)+ 恢复测试 | | `gd5f_test.h` / `gd5f_test_task.c` | GD5F 套件:NAND 驱动 10 阶段 | | `sd2506_test.h` / `sd2506_test_task.c` | RTC 套件:SD2506 RTC 2 阶段组 | | `run_boot_test.py` / `verify_log.py` + `serial_tool` | 自动化闭环三件套(纯 Python 标准库 / 仓库内置) | | `ftp_test.py` / `ftp_capture.py` / `ftp_up_dl_test.py` / `ftp_speed_test.py` | FTP PC 端测试工具 | | `ch395f_socket_test.py` | CH395F PC 端 socket 回显 / 大文件传输工具 | | `time_sync_server.py` | 对时推送 PC 端服务器 | ## 附录 B:模块测试文档 | 文档 | 模块 / 套件 | |---|---| | `docs/测试说明/板级测试/CH395F_Test_Guide.md` | CH395F(11 阶段,TC-NET-xxx) | | `docs/测试说明/板级测试/GD5F2GQ5UE_Test_Guide.md` | GD5F + 存储集成(TC-GD5F / TC-STO) | | `docs/测试说明/板级测试/SD2506_Test_Guide.md` | RTC(TC-RTC-xxx) | | `docs/测试说明/板级测试/FTP_Test_Guide.md` | FTP 服务(手动 + 阶段 8 自动化) | | `docs/测试说明/测试工具/串口助手设计说明.md` | 串口调试工具(Python 串口助手) | | `docs/测试说明/流程与规范/自动化测试流程.md` | /serial-boot-test 闭环细节 | | `docs/测试说明/流程与规范/测试文档模板规范.md` | 测试文档模板与命名 | | `docs/测试说明/单元测试/看门狗单元测试说明.md` | 看门狗判定状态机(主机侧纯逻辑单测) | | `docs/测试说明/单元测试/系统时钟单元测试说明.md` | 系统时钟换算(主机侧纯逻辑单测) | | `docs/测试说明/单元测试/配置文件单元测试说明.md` | 配置引擎解析/契约(主机侧纯逻辑单测) |