
做自动化测试的老哥应该都清楚TestStand在流程编排上确实能打一套序列跑下来失败停线、条件跳转、数据报告一条龙。但落到具体工时上测试工位一多部署授权、操作员界面定制、和既有LabVIEW测试VI耦合这些事往往比写测试项本身还耗神。所以我干脆在LabVIEW 2018上照着TestStand的骨架手写了一套自动化测试系统源码把序列解释、步骤分发、结果上报、报表入库这几条主线在G语言框内还原出来。这套东西的核心价值不在于和TestStand硬碰硬而是告诉你当你的项目只需要TestStand七八成能力的时候自己用LabVIEW搭一套成本可控、源码可改、验收之前随时能调。适合手里已经攒了一堆测试VI、想统一管理又不想让测试项变成意大利面代码的同行参考。1. 自研这套序列引擎前先想清楚TestStand到底帮我们做了什么1.1 TestStand擅长的不是单次测量而是流程TestStand本质上是一个测试执行环境它和LabVIEW是两套独立的产品TestStand负责编排先做什么、后做什么、失败了跳到哪里LabVIEW负责写具体的测量/测试VI。在典型项目里你用LabVIEW写好每个测试步骤VI然后在TestStand里把这些步骤排成一串序列再配置通过失败逻辑、报告模板、数据库记录。所以TestStand帮你处理的核心问题不是怎么测电压、怎么读温度而是三层东西流程控制层顺序执行、循环、条件跳转、调用子序列、并发流程结果管理层每一步的状态Pass/Fail/Error/Skipped、测量值记录、与上下限比对数据出口层测试报告、日志、数据库落库、与MES对接这三层拆清楚之后你会发现很多东西其实是通用框架和具体测什么产品没关系。而这正是LabVIEW自研框架的切入点把这三层原样做一遍业务VI保持独立那这套系统的适用范围就非常宽。1.2 哪些场景值得自研哪些场景别自己折腾我先把话说在前面如果你的项目已经上了TestStand或者公司有平台化的测试架构规划别看了这篇文章就动手重构以下场景是我判断适合自研的项目测试步骤数量中等几十到一百多步流程以顺序执行为主团队对LabVIEW非常熟悉但对TestStand的序列编辑器、变量映射那一套还没形成积累每个测试工位需要深度定制操作员界面TestStand默认UI改起来麻烦预算或交付周期不允许在授权环境、序列开发工具链上耗太多而不适合自研的场景也很清晰几十个工位并发跑复杂流程、序列需要由测试工程师频繁修改而不是改代码、要和MES做深度数据追溯。这些场景下成熟平台的稳定性和维护性远比自己折腾高得多。我这套系统给自己划的边界是核心子集。不做TestStand的完整克隆也不解析.tst文件而是定义了一套自己的序列描述格式XML用LabVIEW实现执行引擎让业务VI只关注测量本身。这套方案在单板信号测试、电源模块功能测试、整机老化记录这几类项目上实测完全扛得住。2. 测试序列的五脏六腑StepRecord类型与序列文件解析2.1 一个步骤到底存哪些字段模仿TestStand的第一步是先定义一个步骤在内存里长什么样。我看TestStand的序列模型提炼出LabVIEW里一个测试步骤必需的字段整理成一个自定义簇Cluster再加类型定义初期够用后面扩展多的时候我改成了LabVIEW Class因为循环嵌套和跳转关系用Class封装起来维护性会好很多。一个StepRecord至少包含六部分信息字段组具体内容说明标识步骤名、步骤ID用于日志和报告定位类型单步VI、循环、分支、调用子序列、结束决定引擎如何解释该步骤调用信息VI路径、输入控件映射、输出控件映射决定动态调用谁执行参数超时时间、重试次数、失败后动作决定步骤行为边界判定模型上下限值、字符串预期、自定义判定VI决定Pass/Fail跳转关系下一步ID、失败跳转ID、循环条件决定流程走向这里最关键的是跳转关系不要写成下一步必须跟在数组后面。TestStand的流程之所以灵活是因为它能任意跳转失败可以跳到某个清理步骤循环可以回到开头。我的数据结构里每个步骤执行完返回一个状态码引擎根据状态码当前步骤配置去查跳转表决定下一个执行谁。2.2 用XML描述序列循环、跳转和嵌套调用序列文件格式我选了XML原因很实在LabVIEW自带的XML解析函数够用而且XML被人眼扫一遍就能看懂测试工程师发现问题可以直接打开序列文件排查不用非得启动软件点半天界面。一个简单的序列文件长这样Sequence Name开机自检序列 Version1.0 Step NamePowerOn TypeVI VISteps\PowerOn.vi Input NameSN SourceGlobal.SN / Output NameVoltage DestinationGlobal.Voltage / Timeout5000/Timeout OnPass NextLoadFirmware / OnFail ActionGoto TargetCleanup / /Step Step NameLoadFirmware TypeVI VISteps\LoadFirmware.vi Input NameSerialPort SourceGlobal.Port / Timeout30000/Timeout OnPass NextCheckVersion / OnFail ActionGoto TargetCleanup / /Step /Sequence循环和条件分支怎么表达我用的办法比较接近TestStand里的循环步骤概念定义一个TypeLoop的步骤里面嵌套一个子序列片段循环次数或退出条件写在节点的条件表达式里。引擎解释到Loop步骤时会维护一个循环计数器每次跑完子序列里的最后一个步骤都回来检查循环条件是否满足。2.3 把配置翻译成内存数据结构序列解析完成后引擎内存里维护的不是二维数组而是一个步骤对象数组 跳转表。跳转表的键是当前步骤ID结果状态值就是下一步骤ID。比如(CheckVersion, FAIL)对应Cleanup就表示版本校验失败直接跳到清理步骤。调用子序列时我用了栈。引擎每次遇到TypeCallSubSequence就把当前序列的上下文序列名、当前步骤索引、数据引用压栈然后加载子序列开始执行子序列跑完或遇到Return步骤时弹栈恢复外层上下文。这个栈在LabVIEW里用一个移位寄存器存数组就能实现清晰且不容易乱。还有一个小设计解析XML时不要把所有节点一次性塞到内存里。对于上百步的序列没问题但一旦序列文件变成几MB解析还是有点慢。我的做法是解析完之后保留原始XML字符串只在切换到某个序列时才真正解析该序列的步骤这样启动速度和内存占用都好一些。3. 执行引擎的骨架状态机、消息队列与动态VI分发3.1 引擎消息定义与主循环执行引擎是整个系统的心脏。我把它做成一个独立VI和UI彻底分离。引擎对外只接收命令命令用LabVIEW队列传递命令集合包括Start启动序列、Pause暂停、Resume继续、Stop停止、StepOnce单步、Abort急停。主循环是一个标准的生产者-消费者状态机。状态有五个Idle、Running、Paused、WaitingManualResult、Stopping。每个状态里会先检查队列有没有新命令再做该做的事。注意命令处理的优先级Abort必须最高Stop次之Pause再低一点Start和Resume不能打断正在执行VI的步骤。为什么用队列而不是用户事件因为队列天然支持多生产者暂停、继续、停止这些命令可以由操作员界面发也可以由上位机通过TCP远程发甚至某个测试步骤VI内部都能发。用户事件虽然也能做但要额外处理注册和注销队列在LabVIEW里的语义更简单直接。3.2 动态调用VI的正确姿势与参数读写这一步是模仿TestStand适配器的核心。TestStand通过适配器调用LabVIEW VI而我们直接用动态VI引用![块图思路Open VI Reference → 属性节点访问控件 → Invoke Node调用 → 读取结果 → Close Reference]具体流程根据步骤记录里的VI路径用Open VI Reference打开引用。注意这里有一个关键选项如果VI不在内存中且路径是相对的LabVIEW会按VI Search Path去搜索。所以我强烈建议序列文件里的VI路径统一用相对于项目根目录的路径并且在程序启动时把项目根目录加到搜索路径里。打开引用后通过属性节点Property Node按名称找到前面板的输入和输出控件。这里约定好命名规则比如输入控件统一叫PARAM_XXX输出控件统一叫RESULT_XXX。这样引擎就能用Control Value Set把上下文里的数据写进去再用Control Value Get读出来。用Invoke Node调用Run VI如果需要等待执行完就用Wait For Completion。这里要注意Run VI方法默认是异步的必须配合等待参数使用如果直接调用而不等待引擎会立刻跑到下一个步骤。调用结束后无论成功失败必须Close Reference。这个后面会说不关引用是内存增长的元凶之一。热搜词里labview路径调用vi怎么传递值问的就是这个场景。答案是不需要修改被调用VI的接口接线端只要前面板上有对应名称的控件动态引用就能读写。这样业务VI可以完全在编辑环境下独立调试进了框架又自动被当成步骤来调度侵入性非常低。3.3 上下文管理让每个测试步骤共享同一份数据自动化测试系统里数据共享是最容易翻车的地方。一个产品测试下来SN号、治具号、操作员、环境温度、每一步的测量值要在几十个步骤VI之间流转。我用的是Data Value Reference数据值引用在LabVIEW里它相当于一个指向数据的引用句柄。引擎在启动时创建一个全局的Data Value Reference里面放一个簇包含SN、型号、当前步骤索引、测量结果数组、配置信息等。每个步骤VI通过输入控件拿到这个引用读写里面的字段。为什么不用功能全局变量FGL因为可重入VI配上FGL会出现严重的数据串扰两个工位同时跑同一个测试VI时FGL只有一份数据互相覆盖。Data Value Reference是每次调用时把引用传进去的只要引用本身不共享各工位就是隔离的。这是多工位并发的关键。3.4 异常中断、暂停恢复与看门狗暂停和恢复我的实现是遇到自然边界再暂停引擎在每执行完一个步骤后检查队列如果收到Pause命令就停在当前状态不继续往下取步骤。正在执行的VI不会被强制打断。对绝大多数产线测试场景来说这个行为是合理的——你暂停的目的是让操作员处理异常而不是把一个正在测到一半的仪器操作截断。超时看门狗是另一个必须有的东西。每个步骤配了Timeout值引擎在启动动态调用时开一个超时定时器。如果Wait For Completion超时还没完成引擎标记该步骤为ERROR然后按OnTimeout的跳转规则走。这里有一个经验不要用Stop VI的方法强制终止正在跑的VI因为这个操作经常让底层仪表通信挂死串口和GPIB资源释放不了。更稳妥的做法是给业务VI注入一个取消标志放到上下文里业务VI在每个采集循环里查询标志主动退出。4. 测试数据的去向日志、Excel报告与MySQL落库4.1 分级日志从Console到文件测试系统的日志和普通程序的调试日志完全不是一个量级的东西。产线上出了问题工程师靠日志还原当时现场是仪器没响应还是测试值超限还是操作员点了跳过。所以日志必须分级普通信息INFO、关键事件WARN、错误ERROR、底层通信字节流DEBUG。我在框架里封装了一个日志lib输出两个目的地UI上只显示INFO级别以上的一行摘要文件里记录完整信息包括时间戳、步骤名、线程/工位ID、消息正文。文件写入有一个细节每写一条日志都要刷新缓冲区。LabVIEW的写文件默认有缓冲如果程序异常退出最后几百条日志可能会丢产线排查问题的时候丢掉尾巴非常致命。串口和仪器通信的字节流日志单独放一个文件内容就是十六进制报文。平时不启DEBUG模式才开。有了这个文件排查通信类测试项的定位速度快很多。这就回应了热搜词里的labview串口通信和labview中log记录。4.2 用Report Toolkit生成格式化的Excel/Word报告报告模块我用LabVIEW的Report Generation Toolkit走的是模板填充路线而不是动态创建Excel对象。这两者的差别很大动态创建Excel每次逐格写入慢而且Excel对象没释放的话进程残留一大堆模板填充先做好Excel模板预留单元格占位符程序打开模板找到占位符填入值最后另存为报告文件模板填充的速度优势在批量测试时很明显一台产品一份报告测完立等可取。占位符我用单元格里写$SN$、$TESTER$这样的格式程序扫描工作表内容遇到$XXX$就替换成实际值。这样测试报告长什么样完全由Excel模板控制客户改动只需要改模板不需要动代码。如果现场没有Office环境这套方案会受限。我的后备方案是用NI Report Toolkit直接生成纯文本HTML报告但视觉效果弱一些。一般来说产线电脑都装了Office问题不大。4.3 MySQL入库的两种连法测试数据要长期追溯必须进数据库。我在系统里接的是MySQL连接方式研究过两条路第一种NI Database Connectivity Toolkit ODBC优点开箱即用SQL语句直接写在VI里不涉及DLL调用。缺点每台部署机都要配置ODBC数据源包括驱动位数、连接串、用户名密码。产线部署最怕这种环境依赖——明明程序打包好了到现场一跑报数据源未找到光排查环境就能耗掉半天。第二种直接调用MySQL C API DLL用LabVIEW的Call Library Function Node封装mysql_real_connect、mysql_query、mysql_store_result这几个函数。优点不依赖ODBC配置只要目标机器装了MySQL驱动库就行缺点要处理指针和内存释放CLN节点定义起来繁琐一点。我的最终方案是第二种封装成了MySQL.lvlib对外只暴露Connect、Insert、Query、Disconnect四个方法。每个方法内部处理DLL调用细节业务VI永远不接触裸DLL。字符集统一设置成utf8mb4建连后执行SET NAMES utf8mb4否则中文产品型号和测试备注进库就是乱码。落库的数据分两张表test_record存每次测试的主记录SN、型号、工位、时间、总结果test_detail存每个步骤的明细步骤名、测量值、上下限、结果、耗时。主记录和明细通过测试批次ID关联。这样查询单个产品测试报告时先查主记录再查明细速度很快。5. 踩过的几个硬坑路径引用、并发工位和内存上涨5.1 动态调用VI最容易掉的链子搜索路径这是我开发过程中卡得最久的一个问题。现象是软件在自己电脑上跑得好好的一复制到产线电脑上有一部分步骤报VI not found另一部分正常。排查之后发现原因就是动态调用VI的路径解析。Open VI Reference传入相对路径时LabVIEW会按它的VI Search Path去搜索。在开发机上项目是打开的工程文件里的路径都注册了但打包部署后VI Search Path里并没有自动包含我程序目录的那个子文件夹于是相对路径找不到。解决方式分两层程序启动时用Application Directory拼出序列目录和测试VI目录调用VI Search Path属性把它们加进去。更保险的方案启动时加载VI注册表。这个注册表是一个配置文件把测试VI的名称映射到完整路径。程序初始化时根据注册表把需要动态调用的VI全部Open一遍并驻留内存。这样运行时不再依赖路径搜索名称匹配就能拿到引用。缺点是多占点内存但换来的是稳定性值得。5.2 多工位并发时小心公共VI的实例冲突热搜词里有人问labview编程实现多个相同测试工位写在同一个软件这个问题比想象中要隐蔽。一开始我在一台工控机上同时跑两个测试工位实例一开就报VI Resource is Reserved原因是被调用的测试VI默认是非可重入的同一时刻只允许一个调用者使用。修复办法是把所有可能被并发调用的测试VI属性里的执行设置为Reentrant Execution可重入执行。这个设置改完之后每个调用者拿到一个独立的VI实例内部数据互相隔离。但随之而来的教训是可重入VI内部绝对不能使用功能全局变量做跨步骤共享。功能全局变量是单例的它不随VI实例隔离。两个工位同时写同一个FGL数据就串了。我排查过一起电压值偶发性错乱的故障最后定位就是子VI里用FGL缓存了校准结果。替换方案就是前面说的Data Value Reference由引擎层负责隔离。5.3 跑了一整夜内存怎么悄悄涨了老化测试跑24小时观察Windows任务管理器发现内存持续上涨最后LabVIEW进程占了近2GB。这类问题绝大多数不是LabVIEW运行时泄漏而是程序里创建的引用没关闭。排查手法每隔一小时记录一次当前运行VI实例数VI Instance Count属性如果持续增长那一定是有VI被反复打开没关。重点检查三个地方动态VI Reference每次调用测试步骤后是否Close Reference打开的文件引用写日志、写报告的引用在错误链末端是否关闭队列引用和事件引用停止引擎时是否全部Flush并Release我后来写了一个资源登记类所有需要关闭的资源在创建时登记到一个数组里程序停止或步骤异常退出时统一清理。虽然不能完全替代人工检查但能兜住很多异常路径下的泄漏。注意LabVIEW里异常路径最容易漏一个步骤VI运行出错Error Cluster一路传递但中间的某个节点可能没执行引用就挂在内存里了。登记表机制能保证即使Error了也有关闭动作。5.4 界面卡死执行引擎和数据采集合一的大忌最早我把引擎状态机、序列解析、测试步骤调度全部放在主VI里旁边再挂一个事件结构处理按钮。结果一运行测试界面就转圈点暂停点不停操作员差点砸电脑。原因很直白LabVIEW的UI线程和执行线程虽然是并行的但事件结构回调里面不能跑耗时操作而引擎调度一旦和事件循环在同一个VI里调度过程阻塞了事件处理界面就卡了。重构方案引擎独立成VI用户界面只做四件事——发命令队列、接收进度消息用户事件、刷新显示、响应用户操作。引擎每执行完一个步骤就发一个当前步骤完成事件UI收到后刷新列表和状态灯。这套架构改完之后再长时间运行界面都保持流畅响应。6. 延伸能力与这套源码目前能扛住的场景6.1 从序列引擎到多工位协调多工位本质上就是同一个框架程序启动多个实例每个实例通过命令行参数或配置文件拿到自己的工位ID然后用这个ID区分别名日志文件、报告目录、数据库记录字段。工位之间原则上不共享运行状态如果一定要共享比如公共治具锁定我用MySQL里的一张锁表实现分布式锁比本机共享变量可靠得多多台电脑之间也能协同。这套系统跑过的场景包括DC-DC电源模块的功能测试线、单板信号测试台、整机EMI摸底记录、老化房定时巡检记录。步骤数量最多的一条序列有86步执行完一轮大约12分钟几百个产品跑下来稳定性和数据完整性都在线。6.2 串口仪表、视觉模块和外部DLL的接入方式框架里的测试步骤VI怎么接入外部硬件原则是底层不穿框架。串口通信照样用VISA初始化串口、读写、关闭都在步骤VI内部完成CRC16校验、大小端转换放公共库这些是热搜词里被反复问到的点实测用VISA进行串口通信时注意读数据前设置合理的Timeout读取用Bytes at Port先查待读字节数避免一直阻塞在VISA Read上。视觉模块作为普通步骤接入用NI Vision采集图像后把检测结果坐标、相似度、条码内容写到RESULT控件引擎自然就能记录和判定。图像文件可保存在测试记录目录下报告里通过链接引用。视觉步骤比普通信号测试慢不少超时时间要给够一般我设置为20秒以上。外部DLL统一封装在CLN层。不要在业务VI里直接放一堆裸CLN节点否则后续DLL更新版本、换函数名时要满项目去找引用。封装成独立的DLL适配库对业务VI暴露的接口不变DLL内部怎么改都行。6.3 在LabVIEW 2018上开发换来的是部署端的轻量最后说打包部署。开发环境是LabVIEW 2018部署机上只需要安装对应版本的Runtime Engine再把程序生成EXE和依赖文件拷过去加上MySQL驱动DLL跑起来就行不需要额外安装授权服务。打包的注意点动态调用的VI不会出现在LabVIEW编译器的依赖列表里必须在项目里把它们单独设定为始终包含否则生成后的程序里根本找不到那些测试步骤VI一运行就报VI不存在。VISA驱动的部署同理它是独立的安装包应用打包时不需要把VISA DLL塞进EXE运行时再安装VISA runtime即可。这套系统的能力边界我也很清楚它做不了TestStand那种企业级的多用户序列协作编辑也不能在运行中热切换到任意序列文件更不用说和成熟的MES深度集成。但如果你要的是一条产线、一组固定序列、一个稳定且可排查的测试记录闭环那用LabVIEW自己搭这套骨架完全可行。我个人实际操作里的体验是这种项目千万不要一开始就追求大而全。先把序列文件格式和引擎最小闭环跑通——能按顺序执行10个步骤VI能报Pass/Fail能写一行数据库——然后把跳转、循环、暂停、报告逐项加进去。每加一项就回归一遍。LabVIEW这种图形化语言的好处是状态机逻辑画出来之后自己和同事都很容易看懂排查问题比读纯文本代码轻松不少。如果后续真要向TestStand迁移因为序列文件的结构本来就是照它设计的习惯来的过渡成本也不会高。