news 2026/9/9 5:31:20

基于ATML与IEEE 1671的自动化测试系统数据驱动架构设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于ATML与IEEE 1671的自动化测试系统数据驱动架构设计

1. 项目背景:传统设备自动化测试系统开发,最痛的是什么

先交代一下背景。过去几年我一直在做设备自动化测试系统的设计与集成,这里的“设备”既有电路板组件、电源模块,也有整机终端。做这套东西的人应该都有同感:早期项目几乎都是“项目制”开发,客户提一个测什么、怎么测的表格,我们就从头写一套测试程序,每种被测对象配一套专门的界面和逻辑。表面看没什么问题,等到了后期维护阶段就开始难受了。

我把这些年踩过的坑归纳成三类,估计在座的同行都躲不开。

第一是程序与测试信息深度绑定。所有测试项、量程、判定上下限全部硬编码在测试程序里。今天电源板输出电压要改成12.5V,我们得翻代码;明天要增加一个测试点,程序结构又得动。改一次程序就要回归一次,工作量大不说,最重要的是容易漏。

第二是仪器互换难。做自动检测系统最头疼的就是仪器停产。客户买的第一批系统用的是台式万用表,三年后换了另一家的示波器,同样是电压读数,接口指令完全不一样。在传统架构里,这意味着要把测试程序里所有测量语句全部重写。更麻烦的是,现场如果不具备重新调试的条件,整个测试站就瘫了。

第三是数据表达不一致。同样是测试结果,有的程序导成Excel,有的存成数据库,字段名五花八门,工艺部门拿到的报告格式都统一不了。等系统要接入工厂级数据平台或做数字溯源时,每一台都要单独开发数据接口。

后来我们在一批新项目中尝试全面引入ATML标准来组织数据、构建自动化测试系统,刚才说的这几个问题基本都得到了解决。ATML是Automatic Test Markup Language的缩写,是一组基于XML的开放标准,由IEEE 1671系列标准定义。它的核心价值不是规定你用哪款软件、用哪家仪器,而是把自动测试系统里涉及的各种“信息”结构化——被测对象信息、仪器能力、测试流程、测试结果、适配器连接路径、测试站配置,全部用统一的XML模式进行定义。有了这个统一的数据底座,测试程序里那堆“硬编码”就能被抽出来变成标准数据,程序变成了一个“读数据并执行数据”的通用平台。

这篇文章面向的是三类人:正在做自动测试系统方案论证的工程师,需要改造存量测试软件的团队负责人,还有刚入行、想了解ATML怎么落地的年轻同事。我会从整体思路到落地实操把一套可复用的构建方案完整展开,重点讲清楚“为什么这样设计”和“实际执行时会卡在哪些地方”。

2. 为什么要用ATML当数据底座:整体设计思路拆解

2.1 ATML解决的不只是格式问题,而是“把测试变成数据”

很多人一开始接触ATML,第一反应是“这不就是规定了一套XML格式嘛”。这种理解不能说错,但低估了它的设计意图。

我们回想一下传统测试程序的结构。一个程序里至少混着四类东西:被测对象的测试内容(测什么、加什么激励、判据是多少)、测试步骤次序(比如先上电后测量)、仪器驱动调用(万用表用哪个命令读电压)和显示存储逻辑(结果怎么写)。这四类东西的生命周期完全不同:仪器命令随硬件换代,测试流程随产品版本变化,判据随工艺要求调整,结果存储格式又随信息化要求改。把它们耦合在一起,本质上是用一套代码去管理四条不同步的变更线,维护成本当然高。

ATML的思路是把这四类东西拆开,各自形成一份独立的数据文档。在IEEE 1671框架下,系统可以生成六类核心文档。

  • UUT描述文档:描述被测对象是谁,接口定义,位号清单。
  • 测试描述文档:描述测什么、按什么顺序测、信号参数是多少,但不关心用什么仪器测。
  • 测试配置文档:描述某个精度的信号能从哪个通道出去、经哪台开关连到哪个仪器端,属于“资源连接关系图”。
  • 仪器描述文档:描述某台仪器具备哪些测量/激励能力和具体指令接口。
  • 测试站文档:描述整个机柜上装了哪些仪器、哪些开关、哪些通道。
  • 测试结果文档:输出被测对象的逐项测试结果、采样数据与判定结论。

一旦测试行为被描述成为数据,后续发生仪器变更时,就只需要把测试描述中的“电压测量需求”重新绑定到新仪器的驱动上,测试内容、流程、结果显示逻辑都不需要改;当被测对象换一个新版本时,增删测试项也只需要改测试描述文档,程序不动。这就是“数据驱动测试”的本质优势——逻辑不变、数据变。

2.2 先盘点信息模型:你到底需要哪些ATML组件

构建方案前别急着写代码。我第一次部署ATML时犯过一个错:一上来就想把所有组件全覆盖,结果光建Schema就拖了半个月。后面想明白了,实际项目里不需要所有组件都一步到位。先按系统边界盘点当前真正需要的信息。

如果你只做一套单机自动化检测设备,最关键的是TestDescription、UUTDescription和TestResults。这三个定义了“测什么”“被谁测”“测出来怎样”,日常收益最大。加上InstrumentDescription和TestConfiguration可以实现仪器自由换,收益进一步扩大,也是ATML在资产复用方面最有说服力的部分。TestStation和Adapter描述可以作为可选组件,等系统要跨台位复用TPS(测试程序集)时再补齐。

有一个技术细节要注意:ATML描述的是“信息模型”,不是“执行模型”。一些团队在读了标准后发现TestDescription里能描述信号,就想要它直接替代执行引擎,这是不对的。ATML解决了“如何表达”的问题,执行引擎负责把表达翻译成可运行的仪器动作,两者各管一段。所以实际系统里ATML数据层与执行引擎(或者叫运行时内核)缺一不可。

2.3 系统分层:四层架构把标准和工程解耦

从工程实践看,我会把系统分成四个层次来设计。第一层是文档与数据层,负责管理所有ATML XML文档、Schema文件和基础数据字典,可以理解为数据仓库。第二层是核心服务层,包括Schema校验器、文档解析器、对象映射器、资源调度器和数据归档器,这一层是平台的中枢。第三层是执行层,包括测试序列引擎、仪器驱动管理、开关路径切换等运行时服务。第四层才是表现与应用层,如操作界面、报表打印、数据透传、与MES对接接口等。

这个分层结构和传统测试软件差别最大的就是第二层与第三层之间多了“核心服务层”。核心服务层里的资源调度器(ResourceAllocator)承担了“把测试需求的信号翻译成具体仪器连接动作”的角色。它读取TestDescription里某一步的电压测量需求,再依据TestConfiguration里配置的物理通道资源,查表确定该信号分派给哪台仪器的哪个端口,最后生成一个可执行的资源分配单。这样可以避免在测试程序里写“去DMM读值”这类绑定语句,改写成“从UUT的PowerPin得到DC电压读数”这样的需求描述。

我通常引用数据库设计里常听的一句话来解释这套架构:由数据驱动行为,由配置驱动仪器。假如今天现场换了一台同精度的仪器,只需要在InstrumentDescription描述库里登记新仪器,再在TestConfiguration里把该信号映射到新仪器的端口上。测试流程文件、被测对象文件、结果输出逻辑全部不动,这就是标准带来的迁移收益。

3. 构建方案的落地路径:从数据模型到引擎实现

3.1 技术栈选择和工程骨架

先说明一下,ATML标准本身不指定任何实现语言。评估半天以后,我们的平台软件选择C#/.NET做主语言,主要原因是Windows平台下仪器控制生态熟、串口/GPIB/LXI/VXI这些总线驱动库都很齐,UI开发效率也高。如果你的底盘是Linux工控机,也可以选Java或Python,只影响实现方式,不影响ATML数据设计的核心。

我习惯的工程组织是这样。

  • Solution根目录下放一个ATML目录,下面按组件类型分子目录:TestDescription、TestConfiguration、UUT、Instrument、TestResults,每个目录里放着对应的XSD Schema及范例文档。
  • 源码层分Solution的项目:Atml.Core(对象模型与XML序列化)、Atml.Validation(Schema校验)、Atml.Relational(关系映射),Atml.Engine(测试执行引擎),Station.Drivers(各仪器驱动插件)。
  • 配置层统一放StationProfile.xml(实际是TestStation的实例文档)以及仪器能力注册表。

这个目录结构不是随便拍的。ATML文档与Schema、驱动插件与引擎、站配置文件与业务代码之间的物理隔离,能让多人团队能并行开发而不会每天都大量合并冲突。

3.2 把ATML文档真正“跑起来”:测试描述文档设计

这里需要细说测试描述文档怎么设计。标准自己的Schema很长,但我们实际用到的字段可以收敛。下面是我在项目中常用的一份测试步骤片段,做了精简去掉一些标准头命名空间后类似这样:

<?xml version="1.0" encoding="UTF-8"?> <TestDescription xmlns="http://www.ieee.org/ATML/2020/TestDescription" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ieee.org/ATML/2020/TestDescription TestDescription.xsd"> <TestProcedure> <EntryPoint name="MainSequence" id="seq_main"> <Next>step1</Next> </EntryPoint> <TestGroup name="MainSequence" id="seq_main"> <Test id="step1" name="DCMeasureAtPowerPin"> <TestSignal type="IEEE1641:VoltageDC"> <Measurement> <DCLevel>12.0</DCLevel> <Nominal>12.0</Nominal> <LowerLimit>11.8</LowerLimit> <UpperLimit>12.2</UpperLimit> </Measurement> </TestSignal> <Pins><Pin>ThePowerPin</Pin></Pins> </Test> <Test id="step2" name="CheckStatusLed"> <Next>step3</Next> </Test> </TestGroup> </TestProcedure> </TestDescription>

很多刚开始写的人会问:既然标准规定要引用IEEE 1641的信号定义,是不是每个测试项都要手工记信号类型名?我不建议让测试工程师直接对着XML写。实际项目可以做一个配置化编辑器,界面提示“测试类型:直流电压测量/开关通断/频率测量”,编辑器自动生成对应的TestSignal节点。这样业务人员不感知XML,写文件的人是一个专门的数据准备角色。

关键点是TestSignal节点里不要写“使用Rigol DM3068进行测量”这类字眼。测试描述里只描述“需要测一个12V的直流电压”,连用什么量程或触发方式尽量也不写。量程和精度由后面的资源调度器根据仪器能力去匹配。要实现仪器互换,这个“不写死”的纪律必须贯穿所有测试一步到底。

3.3 信号需求与资源能力映射,这是交换的关键

“信号需求”这个概念在标准里面很关键。你测直流电压,需求信号叫VoltageDC,带幅值、限值等属性;而现场的某一台万用表,它的能力信号是“电压量程10V、交直流电压、精度0.01%”。调度器的任务就是“匹配”,把信号需求匹配到对应的测量激励能力上。

实际项目第二步要建立“信号能力映射表”。这个映射表通常放在资源调度服务的配置文件里,不建议硬编码。可以用一个XML表或数据库表,保存如下映射行:信号类型为VoltageDC时,对应资源类别为DMM;信号类型为CurrentAC时,DMM的本级电流档位上,且切换需求要求资源具有SwitchingCapability为“DCMode”。如果系统里有任意波形发生器、电子负载等非线性激励设备,则需要进一步细分频段和功率属性。

这个映射表对我们的日常更换仪器特别重要。供应商停产一台万用表,我们采购同级别替代品后要做的动作并不是改上千个测试XML,而是三件事。更新仪器描述库,录入新设备的总线地址、名称及精度。在映射表里把原DMM设备条目指向新设备的能力别名。更新StationProfile中设备对应的SCPI指令前缀或VISA资源描述。这样处理后,整个测试集不用动,经过一轮快速验证,可正常判定。

另外要提醒一句:在描述“信号路径”时,很容易把测试适配器和开关路径搞错,导致仪表选择了通道但信号并没有物理到达。因此测试配置文件中需要包含从“UUT引脚”到“仪器连接器端子”的完整路径,中间每一个开关继电器的节点都要列出。我们项目里有一块自研的开关矩阵板,每条通道都带一个“路径编码”字段,测试配置里把信号路径写成类似“SW01:A1->SW01:B3->DMM:HI”的字符串,调度器执行时先断开所有公共通道,再单点吸合,防止多个继电器的串扰。

3.4 仪器驱动层的封装策略:从VISA到IVI

仪器互换能不能落地,最终要看驱动层封装。我在早期项目用过直接在测试代码里调用SCPI命令的做法。比如MEAS:VOLT:DC? 10,(0.001)直接发给万用表,这样所有协议细节都裸露在程序里,换一种表,字符串完全不同。只能把方法封装成MeasureVoltage(string busAddress, double expected, double range),内部定义IDMM接口方法MeasureVoltage()ConfigureVoltageRange(double range),并用厂商各自类实现。

更进一步建议是采用IVI(Interchangeable Virtual Instrument)风格的驱动接口设计,就算不引入商业IVI类库,也应该仿照IVI的“类驱动+专有驱动”模式。给仪器定义类驱动接口,比如IMultimeter、ISwitch、IPowerSupply都有一组共同的业务方法。每个实际设备实现该接口,注册到仪器描述库时带上COM或者网口地址。资源调度器按信号匹配到的资源类别,直接new对应的类驱动实例。

这里有一个非常实用的心得:如果要追求较大的互换性,给驱动接口的每一个方法都传“测试上下文”对象,里面至少带上测量参数、目标通道和超时时间。不能只传裸极的指令参数,因为不同型号的设备对量程和精度的处理差异往往不小。例如测量13V时,A表你设置30V档,B表最佳量程是20V档。类驱动内部处理档位选择,上层不感知,这种设计能最大程度发挥IVI的核心理念。

实际部署中被忽略的另一个问题是没有驱动插件目录管理。我们的Station.Drivers项目将各个设备驱动编译为一个独立DLL,Engine在启动时通过反射扫描驱动文件夹,由描述文件提供设备型号、厂商、CType等信息,实现“换驱动不用重编译整个软件”。这个动作大概省了很多次返工——工厂现场通常只给我们一两天窗口时间来做升级,如果每次都要重新发布整个测试软件才能加一台设备,我们怕是经常要背锅。

3.5 引擎执行器的最小改造方案

文档与XML都齐了以后,必须有“执行环节”。很多团队容易高估这里的工作量,我们称它为“引擎”,但最开始的实现可以很轻量。

引擎的核心是一个顺序解释器,一行行读TestDescription里的Test节点。每拿到一个Test信号需求,先交给资源调度器。调度器从测试配置中查到设备实例号和端口,然后调用对应仪器驱动的接口方法。这里给出一个用C#描述这个流程的核心片段,实际工程会更复杂但骨架是一致的:

public class TestSequenceExecutor { private readonly ITestStationProfile _station; private readonly IResourceAllocator _allocator; private readonly IDriverManager _driverManager; public TestSequenceExecutor(ITestStationProfile station, IResourceAllocator allocator, IDriverManager driverManager) { _station = station; _allocator = allocator; _driverManager = driverManager; } public TestResultItem ExecuteStep(TestStep step, UutContext uut) { // 1. 将信号需求转换为资源请求对象 SignalRequirement requirement = TestSignalParser.Parse(step.TestSignal); // 2. 通过映射表找到匹配的资源,这里返回的是“能力ID”,不是具体仪器 ResourceReservation reservation = _allocator.Allocate(requirement, _station); // 3. 驱动管理器根据能力ID拿到类驱动实例 IInstrumentDriver driver = _driverManager.Resolve(reservation.CapabilityId); // 4. 对驱动下发测量上下文,并由驱动内部做量程处理 MeasureContext context = new MeasureContext { Expected = requirement.Nominal, LowerLimit = requirement.LowerLimit, UpperLimit = requirement.UpperLimit, Channel = reservation.Channel, Timeout = requirement.TimeoutSec }; double reading = driver.ExecuteMeasurement(context); // 5. 生成一条符合ATML TestResults结构的结果项 return new TestResultItem { TestName = step.Name, SignalName = requirement.SignalType, Value = reading, LowerLimit = requirement.LowerLimit, UpperLimit = requirement.UpperLimit, Passed = reading >= requirement.LowerLimit && reading <= requirement.UpperLimit }; } }

执行引擎的一个重要维护心得是必须做重试和异常隔离机制。仪器读数偶尔会异常,接线松动会超时,如果单步执行异常直接把整个线程退出,对生产测试站来说无法接受。我们在每步Test外面包了三次重试的循环体,并记录真实原始错误信息。经过三分钟测试率后,重试机制已经帮助操作员避免了不少因探针瞬时接触不良导致的假性失败,效果显著。

4. 构建过程中躲不过的坑:问题排查与经验实录

4.1 Schema版本和命名空间的坑

ATML系列标准技术文档分为多个版本,不同代际的Schema命名空间域名不同。我们在早期有个项目把2010版的部分XSD和2016版的实例混在一起用,解析器一直报元素无效错误。排查半天才发现XSD引入的是2010的TestDescription,但XML实例里写的验证名空间是2020版本。

想避坑就需要建立Schema版本基线,在SVN/Git里把每一版Schema固化并打Tag,不“默认使用最新”。每次做解析模块升级时,把旧版本Schema和新版本Schema分开保存,通过命名空间自动选择对应的解析策略。这样可以避免项目供应链上的文件经过传输后版本漂移。

4.2 UUT ID与技术状态

很多刚开始信息化建设的团队在做ATML落地时,只把精力花在测试序列上,结果UUT描述文档里所有对象都叫“产品1”。这样做在技术验证阶段勉强能跑通,真正进入产线就不行了。对大批量混线生产来说,没有准确的UUT技术状态,测试过程数据与产品批次无法一一对应,出了问题没法追溯是哪一批物料。

我们把UUT的编号规则定义好了。UUT文档里不只是零件名,还包含料号、硬件版本、软件版本和关键工艺参数。测试结果文档里记录UUT实例的序列号或条码、操作员、工站编号以及环境温湿度。这些信息被系统采集起来后,报表系统做SPC质量趋势分析时非常有用。

4.3 数据体积与解析性能

ATML文档为了保持可读性往往比较冗长,一个大型测试集动辄几千行XML,如果每次执行前都从零解析并维护DOM树,内存有压力,解析耗时也对高速产线不友好。尤其是对于毫秒级的在线测试节拍,性能感受可能不稳定。

我们的做法是引入二级缓存:程序启动时扫一遍ATML目录,把常用评估数据的模式改为对象模型并缓存于内存;只有当某个文档的文件指纹变化时,才重新解析。同时把一批历史不用的测试过程数据定期归档到数据库或文件库,避免在内存中积压太多现场数据。

4.4 团队配置与标准理解不一致

ATML的语义描述存在比较大的自由度。同样一个“上电测试”,A工程师在TestDescription里写成自定义信号类型PowerSequence,B工程师却写成一组标准信号的组合。从信息表达上各有道理,但对解析器和资源调度器来说,一旦出现自定义类型,就必须扩充“信号能力映射表”。我们项目就出现了整份Schema标准但能力表越来越膨胀,后期维护成本比较大的情况。

后来制定了一个简单约定:任何测试信号需要先拆成IEEE 1641基础逻辑信号;如果基础信号确实表达不了过程控制逻辑,才允许添加自定义业务信号,并统一由评审小组审核自定义词汇。这一条纪律对保持整个系统能力映射的可维护性很有价值。

4.5 一些经验性建议

回看重点要提醒的是,标准化改造不要一上来就推翻所有存量测试代码。我们最好的路径是“一台新设备试水、解决单点问题、再横向复制”。先选产品谱系里的一类典型终端,用ATML标准化重建它的测试集,同时把老程序留在旁边作为金牌参照,拿相同良品跑复测对比,等数据一致性达到预期再推进另外产品线。这种试点策略可以有效减少部门和测试班组的技术抵触。

另一个经验是关于驱动厂商的封闭协议。可以优先选用支持标准SCPI、VXI-11协议通信的设备。目前国内现场很多最新型仪器都已支持LAN端口通信与LXI规范,连接稳定性比老式GPIB卡加转接器高。仪器选型时把总线接口兼容性作为硬性筛选条件,能少很多麻烦。

结合这几套项目实践,我现在的倾向是:凡是新搭建的设备自动化测试系统,不管当前是否有多台台位、是否已经出现仪器停产风险,都直接把数据层搭在ATML的Schema体系上,不要把信息垃圾堆到后面再补。等将来客户提出要对测试过程数据做全程可追溯,或要求同一个测试集迁移到新测试台位时,我们手上的标准化资产就是最大的底气。这种收益不像功能开发那样在验收那一刻爆发,但它会在整个设备的生命周期里替你挡住大量隐性风险。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 5:30:29

CrewAI多智能体实战:从任务拆解到调参避坑的全记录

开头先说句公道话。很多刚接触AI开发的朋友&#xff0c;总以为只要把提示词写得足够详细&#xff0c;单次调用大模型就能解决一切问题。我刚开始做AI工具时也是这个思路&#xff0c;结果写着写着就发现&#xff0c;单靠一个LLM调用&#xff0c;处理稍微复杂的任务就会出现上下文…

作者头像 李华
网站建设 2026/9/9 5:30:24

嵌入式固件启动流程深度拆解与OTA故障定位实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:29:21

Ollama本地部署大模型全攻略:从安装到API调用的完整实战

如果你最近在关注大模型落地这件事&#xff0c;应该会注意到 Ollama 这个名字出现的频率越来越高。它不是一个公司推出的商业套件&#xff0c;而是一个开源的本地模型运行时&#xff0c;简单理解就是&#xff1a;把大模型跑在你自己电脑上&#xff0c;数据不出本机&#xff0c;…

作者头像 李华
网站建设 2026/9/9 5:28:51

Python命令行调试实战:掌握pdb核心技巧,告别print大法

调试这件事儿&#xff0c;估计每个写 Python 的人都有一本血泪史。我早些年也是从 print 大法开始的&#xff0c;后来项目越来越复杂&#xff0c;有些 bug 只在特定参数组合下出现&#xff0c;print 打印一堆中间变量&#xff0c;还得自己在脑补执行流程。直到有一次在远程服务…

作者头像 李华
网站建设 2026/9/9 5:28:25

瑞戈非尼与同类靶向药对比:多激酶抑制剂的临床应用与差异化策略

前阵子科里的MDT讨论&#xff0c;一位晚期肝癌患者的后续方案又被翻了半天旧账。年轻医生问&#xff1a;“索拉非尼吃完进展了&#xff0c;二线能不能直接上仑伐替尼&#xff1f;”这个问题我几乎每个月都会被问一次。其实在很多肿瘤科医生的日常决策里&#xff0c;这种“一类药…

作者头像 李华
网站建设 2026/9/9 5:26:37

DuckDB实战:单机一亿行数据分析性能实测

第一次在一台普通笔记本上跑出SELECT count(*) FROM events&#xff0c;看到结果停在100000000的那一刻&#xff0c;说实话我是愣了一下的。不是因为这个数字本身有多吓人&#xff0c;而是整个查询过程太安静了——没有集群&#xff0c;没有动辄几分钟的任务等待&#xff0c;没…

作者头像 李华