Duke-Tang/firmware-autotest
GitHub: Duke-Tang/firmware-autotest
基于Python和pytest的STM32 I2C传感器固件自动化测试框架,通过寄存器级模拟与故障注入验证驱动层错误恢复能力,支持CI驱动的确定性回归测试。
Stars: 0 | Forks: 0
# STM32 I2C 传感器固件自动化测试框架
一个基于 Python 的测试自动化框架,在寄存器级别模拟 STM32 I2C 温度传感器,其驱动层具有重试逻辑、故障注入压力测试和特性分析报告功能。
该项目旨在演示在无物理硬件可用时,固件自动化测试工程的最佳实践,包括:确定性压力测试、错误恢复逻辑的统计验证,以及由 CI 驱动的回归检查。
## 为什么做这个项目
固件自动化测试工程师将大部分时间花在验证系统**在发生故障时**的行为,而不是在理想路径条件下的行为。本框架反映了真实的现实工作流:
- 一个模拟器替代了 I2C 外设,因此测试可以在任何笔记本电脑或 CI 运行器上运行,无需真实硬件。
- 驱动层通过重试和指数退避机制封装了模拟器,提供了逼真的固件风格错误恢复能力。
- 测试使用带有固定种子的 RNG(随机数生成器)确定性地注入故障,因此相同的输入总是产生相同的判定结果——这是稳定 CI 所必需的。
- 特性分析运行器扫掠故障率范围,并生成人类可读的表格和机器可读的 JSON,用于下游分析。
## 架构
┌────────────────────┐
│ Application code │ read_temperature(), read_device_id()
└─────────┬──────────┘
│
┌─────────▼──────────┐ retry, backoff, statistics
│ I2CDriver │ (device/i2c_driver.py)
└─────────┬──────────┘
│
┌─────────▼──────────┐ register-level emulation
│ I2CSensorSim │ fault injection (NACK rate)
│ │ (device/i2c_sensor_sim.py)
└────────────────────┘
| 层级 | 文件 | 职责 |
|--------------|---------------------------------|-----------------------------------------|
| 应用层 | (调用方) | 调用高层驱动 API |
| 驱动层 | `device/i2c_driver.py` | 重试、退避、错误统计 |
| 硬件模拟层 | `device/i2c_sensor_sim.py` | 寄存器映射、故障注入、日志记录 |
## 项目结构
firmware-autotest/
├── device/ # 固件侧模拟 + 驱动
│ ├── i2c_sensor_sim.py # TMP102 风格的 I2C 传感器模拟器
│ └── i2c_driver.py # 带有重试和统计功能的驱动
├── tests/ # pytest 测试套件
│ ├── test_sensor_basic.py # 功能测试(5 个用例)
│ └── test_sensor_stress.py # 故障注入 / 压力测试(5 个用例)
├── tools/ # 自动化测试工具
│ ├── run_characterization.py # 多场景特性分析运行器
│ └── log_parser.py # UART 风格日志解析器
├── .github/workflows/ci.yml # GitHub Actions CI (pytest 矩阵)
├── requirements.txt
└── README.md
## 快速开始
```
git clone
cd firmware-autotest
pip install -r requirements.txt
# 运行完整测试套件
pytest tests/ -v
# 跨 7 个故障率 × 1000 次试验运行 characterization
python3 tools/run_characterization.py --trials 1000
# 生成示例日志,然后解析它
python3 -c "from device.i2c_sensor_sim import I2CSensorSim; \
from device.i2c_driver import I2CDriver; \
d = I2CDriver(I2CSensorSim(fault_rate=0.2)); \
[d.read_temperature() for _ in range(50)]"
python3 tools/log_parser.py sensor_run.log --json log_report.json
```
## 测试覆盖率
10 个 pytest 用例,覆盖了标称行为和故障模式:
**功能测试 (`test_sensor_basic.py`)**
- 设备 ID 寄存器返回正确的常量
- 室温读数在合理的物理范围内
- 读取无效寄存器会引发 `ValueError`
- 写入只读 ID 寄存器会引发 `PermissionError`
- TX 计数器在每次事务处理时递增
**带故障注入的压力测试 (`test_sensor_stress.py`)**
- 稳定的传感器:零故障 → 零重试(验证重试机制不会被轻易触发)
- 不稳定的传感器:30% NACK 率 → 驱动程序恢复 ≥95% 的调用
- 损坏的传感器:100% NACK → 驱动程序在恰好 `max_retries + 1` 次后放弃
- 重试计数器与预期的尝试次数完全匹配
- `reset_stats()` 能正确地将所有计数器清零
所有测试在干净的机器上运行时间不超过一秒。
## 特性分析结果
`tools/run_characterization.py` 扫掠故障率范围,并生成如下表格:
Fault Rate | Trials | Success % | Fails | Avg Retry | Avg ms | p99 ms
0.00 | 1000 | 100.00% | 0 | 0.000 | 0.05 | 0.18
0.10 | 1000 | 100.00% | 0 | 0.101 | 0.34 | 3.68
0.30 | 1000 | 99.20% | 8 | 0.430 | 0.78 | 4.50
0.50 | 1000 | 93.50% | 65 | 1.005 | 1.62 | 8.10
0.70 | 1000 | 75.40% | 246 | 2.350 | 3.50 | 16.20
**结果解析:** 在 3 次重试的情况下,驱动程序可以完全恢复高达约 30% 的 NACK 率。超过约 50% 时,单靠重试是不够的——真实系统需要硬件级别的缓解措施(更长的总线拉高时间、更低的时钟频率或外设复位)。
同时也会生成 JSON 输出,供下游仪表盘或 CI 趋势跟踪摄取使用。
## CI
`.github/workflows/ci.yml` 会在每次推送和拉取请求时运行完整的测试套件,支持 Python 3.10 / 3.11 / 3.12。每次运行还会执行一项特性分析冒烟测试(每个场景 100 次试验),并将 JSON 报告作为构建产物上传。
## 设计说明
- **为什么要有独立的驱动层?** 它将重试/超时策略与硬件细节隔离开来,因此相同的驱动程序可以位于真实的 STM32 HAL 或此模拟器之上。
- **为什么在压力测试中使用固定种子的 RNG?** 非确定性测试会导致 CI 不稳定。`random.seed(42)` 使故障模式可重现——相同的输入、相同的判定结果,每次运行都如此。
- **为什么使用指数退避?** 线性重试会持续冲击性能下降的总线;指数退避(1ms → 2ms → 4ms)为瞬态故障提供了清除时间。
- **为什么需要日志解析器?** 真实开发板上的真实固件仅暴露 UART 输出。基于正则表达式的解析器将非结构化日志转换为结构化指标,这是硬件在环自动化测试的标准模式。
## 作者
Duke Tang — 作为固件自动化测试工程岗位的作品集项目而构建。
标签:ASM汇编, I2C协议, pytest, Python, STM32, 传感器仿真, 单元测试, 压力测试, 回归测试, 固件测试, 安全规则引擎, 寄存器级仿真, 嵌入式系统, 开源框架, 持续集成, 指数退避, 故障注入, 无后门, 测试框架, 硬件仿真, 确定性测试, 软件质量保证, 逆向工具, 错误恢复机制