ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于非实时系统的硬件自动化测试方案与实战解析

基于非实时系统的硬件自动化测试方案与实战解析 做硬件测试这几年我越来越觉得“非实时系统跑硬件自动化测试”这件事被很多人想复杂了。一提到自动化测试大家第一反应就是实时系统、专用测试机、LabVIEW加PXI机箱好像不花几十万买套设备就做不了。但实际在项目里绝大多数硬件测试场景用一台普通PC跑着Windows或者Linux这种非实时系统配合Python和pytest完全能撑起一套够用、能扩展、还便宜得多的自动化测试方案。这篇文章就是基于我最近做的一个项目——“基于非实时系统架构的硬件自动化测试解决方案”把从架构选型、环境搭建、用例设计到时序处理、问题排查的完整思路和踩坑记录写出来。不管你是刚接触硬件自动化的测试工程师还是被老板要求“用最小成本搞定产线测试”的嵌入式开发这篇文章应该都能给你一些可以直接用的东西。1. 为什么敢用非实时系统做硬件自动化测试1.1 先搞清楚“非实时系统”到底行不行很多人一听“非实时”就觉得不行觉得时间不可控、响应不确定怎么能拿来测硬件。但这里有个关键区别硬件自动化测试和硬件实时控制是两回事。实时控制讲究的是确定性比如电机控制每个周期必须精确到微秒级错过一个周期系统就崩溃。但自动化测试的核心逻辑是“发指令、等响应、验结果”它的时间尺度一般在毫秒到秒级对时间精度的要求远没有实时控制那么苛刻。pytest断言一个串口返回的字符串是否匹配哪怕系统偶尔调度抖动多了几十毫秒对测试结果几乎没影响。我在这个项目里做的第一件事就是把所有被测对象的时序需求列了一张表逐个确认哪些是硬实时必须精确到微秒级哪些是软实时允许毫秒级抖动哪些根本没时序要求只验证功能是否正确。最后发现真正需要硬实时的场景只占不到一成而且这些场景完全不适用于Windows/Linux下的常规自动化方案得单独走MCU自测或FPGA测试通道。剩下九成的场景非实时系统完全能扛住。1.2 非实时架构的三个决定性优势成本优势是最直观的。一套PXI机箱加板卡动辄几十万起步还不算软件授权费。而一台普通工控机或二手商用PC三千块钱就能拿下跑Linux和Python一套全免费。如果被测设备自己有串口或者网口连额外的采集设备都不用买。生态优势被大多数人低估了。实时系统和专用框架往往捆绑紧密文档封闭、案例少遇到问题只能抱着官方手册啃。而Python生态里pytest的插件机制、pyserial的串口库、paramiko的SSH库、requests的HTTP库、pyvisa的仪器控制库随便搜一下都有海量案例。团队里随便一个工程师都能上手不需要专门招一个LabVIEW专家。灵活性更是碾压级别的。测试需求变更是常态今天加一个用例明天换一个固件版本后天要并测三台设备。在非实时架构下这些都是改个配置文件、加个参数化装饰器、写个多线程脚本的事。而在传统方案里往往要改测试程序、重新编译、重新验证一条流程走下来半天就没了。我在项目初期就定了一个原则能用普通PC跑自动化就绝不引专用设备能用Python写的就绝不买商业软件。这个原则帮我省下的预算全花在了购买更多被测设备样机和传感器探头上面对测试覆盖率的提升反而更明显。2. 工具链选型与测试环境搭建2.1 核心软件栈选型和版本搭配这套方案的软件栈清单如下都是我实际验证过的组合操作系统Ubuntu 22.04 LTS也可用Windows 10/11下文会讲区别 测试框架pytest 7.x pytest-html 插件生成测试报告 脚本语言Python 3.10 设备通信pyserial串口、paramikoSSH、requestsHTTP API 数据处理pandas numpy日志分析与数据比对 结果上报pytest-html 自研JSON结果汇总脚本用Ubuntu的原因很简单对开发者友好、命令行工具链齐全、远程管理方便。更重要的是Linux下的串口设备节点是稳定的/dev/ttyUSB0这种形式比Windows的COM口好做自动化映射。Windows也不是不能用如果团队更熟Windows那就在Windows上跑效果一样但串口设备映射这块要额外花点心思。pytest选7.x是因为它的参数化功能非常成熟fixture的作用域控制也很灵活这两点对硬件测试尤其关键。硬件测试和纯软件测试最大的不同是测试用例之间往往不能完全独立比如同一个DUT被测设备的多个用例必须共享初始化状态pytest的fixture作用域机制正好能干净地解决这个问题。2.2 硬件连接拓扑设计被测设备种类多连接方式也不同。我的测试台上常驻的接口有这么几类接口类型设备示例用途备注USB串口MCU开发板、嵌入式主板命令行交互、固件日志输出最常用注意USB转串口的芯片型号网口智能网关、路由器SSH命令执行、Web API调用需配置静态IPUSB直连需要刷机的设备固件烧录、USB枚举测试部分场景需要usbip或虚拟化直通GPIO继电器控制板控制被测设备的电源开关需外接USB转GPIO模块仪器接口万用表、示波器电压、波形采集走高性价比方案就选支持SCPI的台式表这套拓扑的核心思想是所有通信路径都走PC的标准接口不在被测设备和PC之间加任何专用协议转换器。原因有二一是稳定性标准接口的驱动和协议栈最成熟不容易出莫名其妙的问题二是排障方便每一段都是肉眼可见的物理连接逻辑链路出问题时很容易定位。2.3 测试环境安装的完整步骤第一步装系统。Ubuntu 22.04装到一台8GB内存、256GB SSD的上一代商务机上硬盘不用太大反正测试数据都丢到NAS上。装完之后先跑一遍sudo apt update sudo apt upgrade把系统补丁打满。第二步配Python环境。用root容易污染系统Python强烈建议用虚拟环境python3 -m venv hwtest_env source hwtest_env/bin/activate pip install pytest pytest-html pyserial paramiko requests pandas numpy第三步配置串口权限。当前用户加进dialout组避免每次都要sudo才能访问串口sudo usermod -a -G dialout $USER第四步建目录结构。这个结构会贯穿整个项目hw_test_project/ ├── config/ # 设备配置和测试参数 │ ├── device.yaml # 被测设备连接参数 │ └── testcases.yaml # 测试用例的全局参数 ├── drivers/ # 设备驱动层封装 │ ├── serial_driver.py # 串口操作封装 │ ├── ssh_driver.py # SSH操作封装 │ └── power_ctrl.py # 电源控制封装 ├── tests/ # 测试用例层 │ ├── test_boot_test.py # 开机启动测试 │ ├── test_io_test.py # 输入输出接口测试 │ └── test_network.py # 网络功能测试 ├── utils/ # 工具函数库 │ ├── log_utils.py # 日志解析工具 │ └── report_utils.py # 报告生成工具 └── reports/ # 测试结果输出目录这套结构模仿了分层测试架构驱动层负责跟硬件打交道用例层只关心业务逻辑配置层留出参数调整的入口。好处是换了被测设备不用改用例只改配置换了通信方式不用动业务逻辑只改驱动封装。3. 核心用例设计与设备交互封装3.1 设备驱动层怎么写得稳驱动层是整个方案的地基这块写不好上面再漂亮的用例都是空中楼阁。我的经验是驱动层要做到“三个隔离”——隔离通信协议差异、隔离设备型号差异、隔离同步/异步差异。以串口驱动为例最核心的操作是“发命令、等回显、拿结果”。很多人直接写ser.write()和ser.readline()看着简单实际用起来全是坑。我封装的大概是这个样子import serial import time class SerialDevice: def __init__(self, port, baudrate115200, timeout2): self.ser serial.Serial(port, baudrate, timeouttimeout) self.ser.reset_input_buffer() def cmd(self, command, expect, retries3, interval0.1): 发送命令并等待期望的返回内容。 expect: 字符串或字符串列表匹配任意一个即视为成功。 返回: 匹配到的完整回显内容。 for attempt in range(retries): self.ser.reset_input_buffer() # 发送命令时按行发送并追加换行符 self.ser.write((command \r\n).encode()) # 循环读取直到碰到期望关键字或超时 buffer b deadline time.time() self.ser.timeout while time.time() deadline: chunk self.ser.read(1) if chunk: buffer chunk text buffer.decode(errorsignore) for exp in expect: if exp in text: return text raise TimeoutError(fcmd {command} 未在预期时间内返回 {expect}) def close(self): self.ser.close()几个关键点每次发送前先清空输入缓冲区防止上一次的残留数据干扰匹配。逐字节读取而非整行读取很多嵌入式设备的串口驱动是逐字节或分段发送的readline()经常拿不到完整行。期待关键字匹配用“包含”而非“完全匹配”嵌入式设备的命令行回显往往带着\r\n和提示符完全匹配会非常脆弱。失败重试三次硬件设备偶尔会出现瞬时丢包或时序异常一次失败不代表设备坏掉。这套封装看着基础但正是这些细节决定了用例跑100次还稳不稳。我去现场出差排查过好几次测试fail问题最后发现都是因为驱动层读写太粗暴不是设备真有问题。3.2 pytest用例的编写范式设备驱动封装好后写用例就是顺水推舟的事。但我发现很多人写硬件测试用例时习惯不对总把大量业务逻辑堆在用例函数里。我推荐的做法是“配置数据驱动 fixture共享 断言尽可能简单”。假设要测一个设备的上电启动功能上电之后确认串口打印了启动日志并且系统能执行第一条命令。用例可以写成这样import pytest import yaml from drivers.serial_driver import SerialDevice with open(config/device.yaml, r) as f: device_cfg yaml.safe_load(f) pytest.fixture(scopemodule) def serial_dev(): dev SerialDevice(**device_cfg[serial]) yield dev dev.close() pytest.mark.parametrize(command,expect, [ (help, help), (version, v1.2.3), (uptime, up), ]) def test_basic_commands(serial_dev, command, expect): result serial_dev.cmd(command, expect) assert len(result) 0一个fixture管整个模块的生命周期不同的命令通过参数化批量执行。如果某个命令返回的内容需要后续处理比如解析版本号、提取IP地址那么可以在驱动层加一个返回数据的解析方法不要在用例里做字符串截取这类重逻辑。再强调一下fixture作用域的选择scopemodule意味着同一个测试文件里的用例共享一个串口连接。这非常关键因为反复开关串口会占用系统资源和设备端缓冲区容易引入额外的不稳定性。但也要注意共享连接后用例之间必须能“自我修复”也就是每个用例开始前通过reset_input_buffer清理残留数据防止上一个用例的回显污染当前结果。3.3 测试数据与预期结果的比对策略硬件测试的最后一个环节是数据比对这里也有讲究。直接assert相等是最简单的方式但硬件测试里几乎不会出现理想化的完全相等。比如测一个供电电压名义上是3.3V实测可能3.29V也可能3.31V这都在允许范围内。我的做法是凡是模拟量数据一律用容差比例判断凡是字符串返回值用关键字包含判断只有二进制文件的校验才用严格相等。def assert_in_range(value, expectation, tolerance_percent5): low expectation * (1 - tolerance_percent / 100) high expectation * (1 tolerance_percent / 100) assert low value high, \ f{value} 不在允许范围 [{low:.2f}, {high:.2f}] 内 def assert_keywords(text, required_keywords): for kw in required_keywords: assert kw in text, f缺少预期关键字: {kw}这套策略看起来简单但我见过太多团队在比对环节抠得太死导致测试天天报fail最后大家都学会了“屏蔽失败”等于是自欺欺人。合理的容差不是放松要求而是符合硬件的真实物理特性。4. 非实时系统的时序与稳定性处理4.1 串口交互中的常见时序陷阱非实时系统最大的挑战不是“能不能测”而是“怎么稳定地测”。最典型的问题就是串口交互的时序竞争。比如你发送一条重启命令设备开始重启串口链接断开重连此时PC端串口缓冲区里残留着设备重启过程中的打印信息下一条命令发送过去后匹配逻辑可能会被残留数据干扰。解决这个问题的核心思路是“显式等待”而不是“盲目sleep”。我在项目里专门封装了几个等待函数def wait_for_serial_reconnect(dev, try_command, expect, timeout15): 等待设备重启完成尝试执行命令直到返回预期结果。 deadline time.time() timeout while time.time() deadline: try: dev.ser.reset_input_buffer() dev.ser.write((try_command \r\n).encode()) buf b while time.time() deadline: chunk dev.ser.read(1) if chunk: buf chunk if expect in buf.decode(errorsignore): return True except serial.SerialException: time.sleep(0.2) raise TimeoutError(设备在预期时间内未恢复串口响应) def wait_for_log_keyword(dev, keyword, timeout10): 等待串口日志中出现指定关键字。 return dev.expect_keyword(keyword, timeouttimeout)这里的核心原则是等待条件而不是等待时间。sleep虽然简单但它没法应对设备启动时间的波动——慢的时候20秒才起来sleep 15秒就假fail了快的时候3秒就起来sleep 15秒又浪费时间。用条件等待既能增强稳定性又能缩短整体测试时长。4.2 日志采集与去抖动方案非实时系统上的日志采集也是个容易翻车的地方。PC端串口工具直接保存日志往往会遇到两个问题一是设备重启时大量日志瞬间涌入PC来不及处理buffer溢出丢数据二是设备端日志输入过快USB转串口的驱动层丢包。我的方案是在“中间层”加一个生产者-消费者模型读线程从串口持续读取数据写入内存队列主线程从队列里做关键字匹配和数据解析。这样即使设备在短时间内输出大量日志也不至于因为pytest的同步等待机制而丢失。import threading import queue class SerialLogMonitor: def __init__(self, device): self.device device self._queue queue.Queue() self._running False self._thread None def start(self): self._running True self._thread threading.Thread(targetself._read_loop, daemonTrue) self._thread.start() def _read_loop(self): while self._running: try: data self.device.ser.read(256) # 按块读取减少线程切换 if data: self._queue.put(data) except Exception: pass def stop(self): self._running False if self._thread: self._thread.join(timeout2)这个设计有两个好处。第一串口数据的读取从“按需”变成“持续”不会因为测试用例在等待某个回复时阻塞了读取线程而导致数据积压。第二数据进入队列后解析逻辑可以往后推不影响采集的连续性。实测下来启用这个监控器后设备在快速连续打印的场景下比如开机阶段每秒几百行日志日志的完整率从原来的85%左右提升到接近100%而且测试用例的稳定性提升非常明显。4.3 超时参数和重试机制怎么设才合理非实时系统的另一个特点是性能波动有时候系统负载高测试用例执行速度会变慢。如果超时时间设得太死稍有波动就fail设得太宽设备真卡死时又要等半天。这个平衡需要经验我给一个合理范围参考操作类型建议超时说明串口命令执行3~5秒正常命令响应应该在1秒内留足余量设备重启等待30~60秒不同设备的启动时间差异大固件烧录120~300秒烧录时间长且需要区分阶段网络请求10~15秒局域网内API调用一般2秒内返回文件传输30~60秒大文件传输需要更多时间超时之外重试机制也要设计得聪明。我的经验是重试必须有“退避”策略每次重试的等待时间递增而不是恒定间隔。比如第一次失败等2秒第二次等5秒第三次等10秒。这样做是因为设备刚重启完的一段时间内系统负载很高立即重试往往还会失败等一段时间让它稳定反而成功率更高。5. 常见问题与排查方法5.1 串口设备无法打开怎么办这个问题几乎每个人都会遇到。排查顺序一般是检查设备是否被系统识别ls /dev/ttyUSB*或ls /dev/ttyACM*查看设备权限ls -l /dev/ttyUSB0如果不是dialout组用sudo usermod -a -G dialout $USER添加后重新登录检查是否被其他进程占用sudo lsof /dev/ttyUSB0有输出说明被占用杀掉占用进程或用fuser -k /dev/ttyUSB0确认USB转串口芯片型号用dmesg | grep usb看内核日志常见的有CH340、CP2102、FT232不同芯片的驱动支持情况不同一个容易被忽略的坑是某些廉价的USB转串口线在大流量传输时会丢数据表现为测试偶尔fail、偶尔pass毫无规律。解决办法是换用FT232或CP2102核心的线成本多花二十块钱稳定性提升不止一个档次。5.2 用例偶发失败的排查思路偶发失败是硬件自动化测试最烦人的问题。一个用例跑十次九次通过一次失败这种问题排查起来比全失败还难。我的排查路线是这样的第一步复现并抓全日志。把pytest的-s参数打开确保print输出不打折扣同时把串口日志完整保存。如果偶发问题不好复现就写一个循环跑50次的压力测试用例提高复现概率。第二步对照失败时间点和串口日志。看失败时设备有没有异常输出、有没有总线复位、有没有重连记录。很多时候设备端的日志能直接暴露原因比如看门狗触发、电源跌落、过热保护。第三步排查时序。用时间戳标注每个关键操作的执行时间看失败时有没有明显的延迟抖动。比如某个操作平时只要0.5秒失败那天要2.3秒说明系统负载或设备响应出现了偏差。第四步隔离环境。换一台机器跑同样的用例如果不再失败那大概率是原机器的USB供电不稳定或驱动版本问题。如果换了机器还在偶发那就是被测设备本身的问题。去年处理过最典型的案例一个用例偶发失败排查了两天最后发现是设备外壳接地不良导致ESD干扰USB通信偶尔被静电打断。这种问题从软件日志里几乎发现不了只能逐步隔离硬件环境。5.3 环境变量与全局配置管理建议随测试规模扩大配置文件的管理也会变成负担。我见过一个项目配置文件散落各目录同一参数在五个文件里重复定义改了其中一个忘了其他结果测试结果全部对不上。我的经验是所有试验参数统一收拢到一个YAML配置中心用例代码里不允许出现裸数字。即使某个参数只有一处在用也必须在配置文件里声明。同时配置文件分两层device.yaml管设备相关testcases.yaml管测试逻辑参数。另外建议把固件版本号、硬件版本号、连接拓扑、测试开始结束时间这些上下文信息直接注入到测试报告里。这样一份测试报告拿出去无论是给研发还是给生产看信息都是完整的不需要再翻聊天记录或邮件去查当时测的是哪个版本。6. 这套方案的扩展方向和适用边界6.1 多设备并行测试的落地思路当测试规模变大、被测设备增多时纯串行执行会拖慢节奏这时候就要考虑并行。非实时系统的多线程、多进程在硬件测试里完全够用但要注意资源的分配。我试过的做法是为每台设备分配独立的串口和独立的pytest进程通过pytest-xdist插件进行分布式执行。这里的关键约束是每个worker进程必须有独立的设备连接和独立的日志文件否则日志混乱、串口抢占会让整个测试雪崩。# 4台设备并行测试的启动命令 pytest -n 4 --dist loadscope \ --device-idDEV01 --device-idDEV02 --device-idDEV03 --device-idDEV04并行带来的提速非常可观4台设备同时跑原本1个小时的回归测试可以缩短到20分钟以内。但代价是出了问题时的日志关联会更复杂所以每个worker的日志文件命名必须带上设备标识。6.2 与CI/CD的集成如果团队有持续集成的基础这套方案还能更进一步每次固件构建成功后自动触发硬件回归测试测试结果自动回传到开发平台。具体实现不复杂Linux服务器上配好定时任务或webhook触发器测试结束后写个脚本解析pytest的JSON结果把pass/fail、失败用例列表、日志链接一起推送到团队的消息群里。这样一来开发提交代码后不再需要手工通知测试人员去跑回归固件质量和自动化测试的反馈形成了一个闭环。我在这个项目里落地了类似机制后回归周期从两天缩短到半天很多低级问题在合入主干前就被拦截了。6.3 什么场景还是得用实时系统诚实地讲非实时方案不是万能的。下面这几类场景建议还是老老实实上实时系统或专用设备需要微秒级精度的信号采集和波形分析比如电源纹波测试标准里要求的精确采样高精度的多通道同步采样比如同时采集多个ADC通道的数据做相位对齐需要硬实时响应的闭环测试比如给设备注入实时故障信号并精确测量恢复时间在这些领域Windows/Linux的调度抖动已经超出了可接受范围强行用非实时方案测出来的数据既不准确也没有说服力。判断标准很简单如果测试结果的有效性依赖于时间戳的精确性那就要谨慎如果只是功能验证非实时方案完全可以覆盖。7. 最后分享一个我最近的实操心得测试环境的稳定性不是一蹴而就的它是在一次次踩坑中逐渐变稳的。刚开始这套方案跑起来时我也经常被各种偶发问题折腾到怀疑人生。但现在回过头看那些问题的根源无非就三类驱动封装不够健壮、时序处理不够严谨、环境因素没有隔离干净。每一类都有对应的解决套路关键是要有一套结构化的排查思路而不是遇到问题就打补丁。如果你正在评估要不要用非实时系统搭硬件自动化测试我的建议是先拿一个最简单、最稳定的用例跑通全链路感受一下这套方案的节奏再逐步扩展。等你能用pytest稳定控制设备完成十几种操作的自动化回归时你会发现自己已经回不去那个纯靠手工敲命令、盯串口、复制粘贴日志抓虫的年代了。这套方案不一定是最顶级的但一定是最“值得”的——投入产出比拉满而且团队里任何人都能上手改用例、加设备、扩展功能。
返回列表