使用TestStand创建测试程序时,核心测试功能在单独的代码模块中实现。TestStand提供的适配器可调用使用各种编程环境和语言(例如LabVIEW、LabVIEW NXG、LabWindows™/CVI™、C#、VB .NET、C/C++和ActiveX)开发的代码模块。
本文档讨论了开发测试系统代码模块并从测试序列中调用这些模块时可能会用到的最佳实践。要使用本文档,您需要掌握TestStand的基本工作原理,其中包括创建基本测试序列的方法。
内容
· 确定代码模块开发的策略
· 选择要实现功能的位置
· 实现代码模块的最佳实践
· 使用代码模块中的仪器
· 查阅TestStand高级架构系列的其他章节
1 确定代码模块开发的策略
开始开发测试系统之前,请考虑为测试系统的以下方面确定一种通用方法:
· 代码模块的粒度 — 定义每个模块的功能范围。
· 定义测试代码的目录结构 — 定义明确的目录结构使开发人员之间可以更轻松地共享代码,且更易于测试系统的代码部署。
代码模块的粒度
在设计测试系统时,请务必为代码模块定义一致的粒度级别。 粒度是指测试系统中每个代码模块的功能范围。 粒度低的测试序列调用的代码模块数量少,每个代码模块执行的功能更多;而粒度高的测试序列调用的代码模块数量多,每个代码模块执行的功能更少。
低粒度
· 代码模块更少,更易于维护
· 代码模块调用次数更少,因而提高了性能
高粒度
· 序列文件的可读性提高,但粒度过高的序列会导致混乱
· 更容易隔离代码模块中的问题和漏洞
由于二者各有优势,因此您应该在这些极端情况之间取得平衡。
利用不同的粒度级别实现简单测试
为了在整个测试系统中保持一致的粒度,需要为代码模块开发创建一组标准,例如:
· 在单独的代码模块中执行硬件初始化和关闭,让TestStand管理硬件会话的生命周期。
· 为每个需求项目创建单个测试步骤,从而根据测试需求确定粒度。 这种方法可以更轻松地确保所有要求均得到满足。 此外,NI Requirements Gateway可与TestStand搭配使用,创建测试步骤与需求文档之间的关联。 如需更多信息,请参阅《NI Requirements Gateway与TestStand搭配使用》教程。
· 所需的测试结果结构可用于帮助确定各个步骤的范围。 由于每个步骤都会生成一个结果条目,因此创建测试步骤到所需结果条目的一对一映射将使组织测试结果变得更容易,且对报告或数据库记录的更改最少。
定义序列文件和代码模块的目录结构
指定测试步骤中代码模块的路径时,可选择使用绝对路径或相对路径。建议不要使用绝对路径,原因如下:
· 在磁盘上移动序列文件及其依赖项后,相应路径将失效。
· 如果将序列文件部署到目标计算机,除非文件安装在同一位置,否则相应路径将失效。
指定相对路径时,TestStand会使用搜索目录列表来解析路径。 这些搜索目录通常包含当前序列文件目录、TestStand特定目录和系统目录。
在开始开发之前,请务必为测试序列和代码模块定义文件结构。 请按照以下指南定义用于存储序列文件和代码模块的策略。
· 对于用于单个序列文件的代码模块,请将代码模块文件保存在与序列文件相关的子目录中。 这将确保,即使序列文件在系统中移动或复制到另一个系统上,它也能够找到代码模块。
· 对于在多个相关序列文件之间共享的代码模块,如果相关序列文件保存在同一目录中,则可以使用与单个序列文件相同的方法。 考虑创建一个工作区来容纳所有相关的序列文件和代码模块。
· 对于在多个不相关的序列文件之间共享的代码模块,请考虑创建一个特定目录来容纳所有共享的代码模块,并创建指向此位置的新搜索目录。 这将确保系统中的所有序列文件都可以使用此搜索目录的相对路径来找到相应文件。 部署代码模块时,可同时部署位于
定义目录结构,其中代码模块位于序列文件的子目录中
使用TestStand部署工具部署测试代码时,可为序列文件和相关代码模块选择特定的目标。 如果序列文件和代码模块的目标目录之间存在相对路径,则TestStand部署工具会更新序列文件中的路径来指向更新后的位置。 在大多数情况下,最好使部署的目录结构与开发系统上的目录结构相匹配,从而确保部署与开发计算机上的代码尽可能相似。
2 选择要实现功能的位置
在定义测试系统的代码模块范围时,请务必定义在代码模块和序列文件中实现功能的策略。以下部分可帮助确定最适合实现常用功能的位置:
· 根据极限评估测试测量值
· 定义激励值
· 报告并记录测试结果和错误
· 循环操作
· 执行开关操作
· 执行计算和处理数据
评估极限和测试结果
理想情况下,代码模块应包含与获得测试测量值直接相关的功能,并且测试序列应处理原始测试结果。这种方法具有以下优势:
· 由于在序列文件中可使用属性加载器等工具在单个中心节点管理多个步骤的极限,因此测试极限在序列文件中更易于管理。
· 序列中定义的测试极限将自动添加到测试结果中,例如报告或数据库。
· 测试极限可在不更改代码模块的情况下进行更新,并且由于只修改了测试序列,因此所需的验证更少。
为了简化测量,代码模块可以将原始测量值返回到序列中进行处理。例如,如果测试步骤用于测量待测设备(UUT)特定引脚上的电压,则代码模块应返回测量值,而不是直接在代码模块中执行检查。 您可以使用数值边界测试步骤来处理该值,从而确定序列文件中的测试结果。
在测试步骤中评估极限可简化代码模块并改进结果记录
但是,由于某些测试十分复杂,因此并非总是能够在序列文件中处理原始测试结果。 对于更复杂的测量,可能需要对结果数据进行进一步处理。 复杂数据可处理为单个字符串或数值结果,然后便可以在TestStand中使用字符串或数值比较功能对其进行评估。 例如,扫频测试的结果很复杂,无法直接进行评估,但可以将这些数据处理为代表最小值的单个数字。 在这种情况下,代码模块应评估处理后的结果,并以单独的参数形式返回频率数据,以便记录,如下面的移动设备测试示例所示:
对于更复杂的数据,请在代码模块中处理数据,生成数值或字符串结果,然后使用参数传出原始数据,以便记录
如果原始数据非常大,则将数据传递给TestStand的过程可能会对性能产生重大影响。 在这种情况下,可考虑将数据直接记录到TDMS文件中,并在测试报告中添加指向该文件的链接。 如此一来,可在无需将数据传递给TestStand的情况下从报告中引用数据。 关于此方法的更多信息,请参阅《在报告中添加超链接 — TDMS文件》。
如果该步骤无法使用测试步骤中可用的评估类型来确定测试结果,请考虑创建具有附加功能的新步骤类型来处理所需的测试类型。 关于创建自定义步骤类型的更多信息,请参阅本系列中的《自定义步骤类型开发的最佳实践》一文。
定义测试激励
对于许多测试而言,UUT或测试环境必须处于特定状态下才能执行测试。 例如,进行温度测量可能需要使用激励电压,或者必须将加热室设置为指定温度。 对于这些类型的模块,应使用参数来传递输入值,例如激励电压或所需温度。 与上一部分所述的直接在代码中处理极限相比,这与在测试代码模块中返回原始数据具有许多相同的优势。
记录测试结果
TestStand具有使用测试步骤的结果来生成报告和记录数据库的内置功能。因此,请避免直接在代码模块内进行任何类型的数据记录。 相反,请确保将要记录的所有数据以参数形式传出,并使用TestStand记录相应数据。 一些数据(例如测试结果、极限和错误信息)将自动记录。 要记录其他数据,可使用其他结果功能来指定要添加到报告中的其他参数。
关于将结果添加到测试报告中的更多信息,请参阅TestStand随附的《将自定义数据添加到报告》示例。
如果您对记录有特定要求,请考虑修改或创建结果处理插件。如此一来,可以使用内置的TestStand结果收集功能收集结果,同时可以确定结果的处理方式和显示方式。如需更多信息,请参阅《TestStand过程模型开发和自定义的最佳实践》文档的“创建插件”部分
循环操作
由于实现循环的每种方法都有其自身的优缺点,因此很难确定理想的方法。请使用以下指南来帮助确定最适合您的应用程序的策略:
在代码模块中进行内部循环
· 性能得以提高,尤其是在快速循环时。 由于每次代码模块调用都会产生几毫秒的开销,因此使用外部循环进行数百或数千次迭代循环会影响测试速度。
· 支持更复杂的循环行为。
在序列文件中进行外部循环
· 直接在序列文件中查看和修改循环设置,不需要修改代码模块。
· 轻松访问序列文件中的循环索引。 这对于确定基于当前迭代而变化的开关路由或其他行为很有用。
· 循环的每次迭代均单独记录,在报告或数据库中显示每次迭代的结果。
执行开关操作
许多测试系统可借助开关功能使用单个硬件测试多个待测区。 借助预定义的路由,开关功能支持以编程方式控制连接到特定硬件的待测设备(UUT)引脚。
您可以通过以下方式在TestStand代码模块中实现开关功能:
· 使用步骤的内置开关属性(需要NI Switch Executive)
· 使用TestStand IVI Switch步骤(仅适用于32位TestStand)
· 直接在代码模块中调用开关驱动程序函数
使用NI Switch硬件时,NI Switch Executive可用于快速定义路由。在可以访问NI Switch Executive的情况下,使用内置的步骤设置来实现开关功能通常是最佳方法,此方法具有以下优势:
· 在步骤中定义开关配置可以将开关函数与测试代码分离,从而提高可复用性并降低代码模块的复杂性。
· 开关设置中的许多字段都是通过表达式指定的,因此可利用RunState.LoopIndex属性或其他变量来为要迭代的步骤建立路由或路由组名称的索引。
· 对于并行测试,可在路由字符串中添加测试插槽索引(RunState.TestSockets.MyIndex),对每个测试插槽使用不同的开关路由。
· 连接生命周期可与步骤、序列、线程或执行关联起来。
使用NI Switch Executive直接在TestStand步骤设置中指定路由,包括TestStand表达式支持,从而使用当前循环索引或其他属性来动态确定路由
关于使用开关步骤属性整合开关功能的更多信息,请参见《借助NI Switch Executive加速开发并简化维护》在线教程。
执行计算和处理数据
为了避免为较简单的任务维护代码模块,可使用TestStand中的表达式语言来执行基本计算和单维数组操作。由于编程语言提供了更适合这些任务且更强大的功能,因此应在代码模块中执行更高级的编程要求。 例如,与使用表达式语言相比,使用原生LabVIEW创建数组函数可更轻松地完成多维数组的连接。
在某些情况下,可使用.NET框架随附的原生类避免创建过于复杂的表达式。 例如,System.IO.Path类可在无需创建代码模块的情况下用于快速执行路径操作。
无需代码模块的参与,即可借助.NET步骤来使用.NET框架方法
3 实现代码模块的最佳实践
实现代码模块时,许多设计决策都会影响创建的许多代码模块。 本部分提供有关以下概念的指南:
· 将数据从TestStand传递到代码模块
· 处理代码模块中的序列终止
· 向TestStand报告代码模块错误
· 管理代码模块的执行速度和内存使用
将数据从TestStand传递到代码模块
访问代码模块中的TestStand数据的方法有两种:
· 通过代码模块参数传递数据
· 使用TestStand API在代码模块内直接访问数据
在大多数情况下,与使用TestStand API直接访问数据相比,使用参数传递数据是一种更好的方法,具体原因如下:
· 出错可能性更低 — 由于参数值是在TestStand的步骤类型设置中定义的,而不是直接在代码模块中定义的,因此更容易发现属性名称或数据类型中的错误。
· 更易于维护 — 对步骤属性的更改是在TestStand的参数配置中指定的,无需修改代码模块。
· 更容易在TestStand之外复用 — 由于代码模块不依赖于TestStand API,因此模块无需修改即可在TestStand之外使用
尽可能使用参数将所需数据传递到代码模块
但是,根据步骤的状态,当代码模块需要动态访问各种数据时,使用API直接访问属性可能很有帮助。 在这种情况下,使用步骤参数会导致参数过多,而在不同情况下,实际用到的参数只有一部分。
如果要在代码模块中使用TestStand API,则需要向SequenceContext对象(ThisContext)传递一个引用作为参数。SequenceContext对象可访问所有其他TestStand对象,包括TestStand引擎和当前的Runstate。如果使用终止监视器或模态对话框VI,则需要序列上下文引用。
使用SequenceContext访问代码模块中的TestStand API,可用于以编程方式访问数据
如果要在TestStand之外复用代码模块,请记住,只有在从TestStand序列调用相应模块的情况下,才能使用TestStand API进行操作。模块通过API从TestStand获取的所有数据将不可用。 从TestStand外部调用代码模块时,可先检查序列上下文引用是否为空,从而定义获取测试数据的备用机制。 在LabVIEW中,可以使用Not A Number/Path/Refnum?函数,它会返回一个布尔值,如图3所示。
对于在TestStand之外使用的代码模块,请使用Not a Number/Path/Refnum?检查SequenceContext对象引用的有效性
在代码模块中处理大型数据集
在许多情况下,代码模块会在测量或分析过程中生成大量复杂数据。 由于TestStand会在存储此类数据时创建数据副本,因此请避免将此类数据存储在TestStand变量中。 这些副本可能会降低Runtime性能和/或导致内存不足错误。 使用以下方法来管理大型数据集,即可避免创建不必要的副本:
· 在代码模块内处理大型数据集,例如在获取数据的同一代码模块中分析数据,并且仅向TestStand返回所需的结果
· 在TestStand和代码模块之间传递数据指针。 对于LabVIEW代码模块,请使用数据值引用(DVR)
处理代码模块中的序列终止
用户按下“终止”按钮时,TestStand会停止执行序列并运行所有“清理”步骤。但是,如果执行调用了代码模块,则该模块必须完成执行并将控制权交回TestStand,然后序列才能终止。如果代码模块的运行时间超过数秒,或者模块需要等待用户输入之类的条件发生,对于用户来说,终止命令可能会被忽略。
要解决此问题,可以使用终止监视器,让代码模块检查并响应调用执行的终止状态。例如,“计算机主板测试”随附范例会使用仿真对话框中的终止监视器,如下图所示。 如果测试序列终止,则检查终止状态VI会返回假值,循环停止。
关于使用终止监视器的更多信息,请参阅终止监视器示例。
处理错误
测试系统中的错误是非预期Run-Time行为,会妨碍测试的执行。代码模块产生错误时,请将该信息传回测试序列,以此确定下一步要执行的操作,例如终止执行、重复上一次测试或提示测试操作员。
要向TestStand提供来自代码模块的任何错误信息,请使用步骤的Result.Error容器,如下图所示。 执行每个步骤之后,TestStand都会自动检查此属性来确定是否发生错误。 无需将错误信息从TestStand传递到代码模块。如果代码模块向TestStand返回错误,则执行过程会引出分支到测试序列的另一部分,例如“清理”步骤组。
您可以使用“测试站选项”的“执行”选项卡中的“Run-Time错误”设置来确定TestStand响应步骤错误的方式。 通常,在开发用于协助调试的序列时应使用“显示对话框”选项,因为此选项可中断执行并检查序列的当前状态。 对于已部署的系统,请考虑使用“运行清理”或“忽略”选项,而不是要求测试操作员进行输入。 错误信息将自动记录到测试结果中,可用于查找错误的原因。
将错误信息传递到Step.Result.Error容器,用于通知TestStand是否发生了步骤错误
管理代码模块的性能和内存使用
默认状态下,在文件中执行序列时,TestStand会将序列文件中的所有代码模块加载到内存中,并保持加载状态,直到关闭序列文件为止。 使用这些设置后,如果在模块加载的同时开始一个序列,则会出现初始延迟。 但是,由于模块仍在内存中,因此序列文件的后续执行会更快。
步骤设置窗格的“运行选项”选项卡可用于配置何时加载和卸载代码模块。通常,默认的加载选项可提供出色的性能,但是在某些情况下,可将加载选项设置为动态加载,从而使代码模块仅在使用时才加载,这可能是一种更好的选择。 对于不在常用执行中调用的代码模块,例如仅在特定测试失败后才运行的诊断,应采取动态加载方式,因为在大多数情况下这些模块根本不需要加载。
请注意,在动态加载代码模块时,TestStand只会在加载代码模块之后才会报告相应代码模块的问题,此时漫长的执行过程可能即将结束。但是,可以在执行之前使用序列分析仪验证序列中是否存在错误。 分析仪将检查静态和动态加载的代码模块。
对于内存密集型代码模块,可修改默认的卸载选项来减少总内存使用量。 例如,将模块设置为步骤执行后卸载或序列执行后卸载。 但是,此更改将增加执行时间,因为TestStand需要为每次后续调用重新加载模块。如有可能,可以使用64位版本的TestStand和具有更多物理内存的系统,这种方法更好,在对内存使用量有较高要求的情况下仍获得出色的测试性能。
如果代码模块维护共享数据,例如静态变量或LabVIEW功能全局变量,由于在模块卸载时会丢失全局数据,因此修改卸载选项可能会导致行为改变。更改卸载选项时,请确保将任何必需的数据传递到TestStand序列或存储在更为永久的位置,防止数据丢失。
关于优化测试系统性能的其他方式的更多信息,请参阅《提高NI TestStand系统性能的最佳实践》。
4 使用代码模块中的仪器
代码模块的常见用途是与测试硬件连接以设置激励并进行测试测量。 与硬件通信的方法包括:
• 使用硬件驱动程序(例如NI-DAQmx)直接与硬件通信。
• 使用仪器驱动程序,此驱动程序可在内部通过VISA或IVI硬件驱动程序将命令发送到仪器。
所采用的通信方式取决于使用的硬件类型。 对于这两种通信,您都需要在进行特定驱动程序的调用之前打开针对驱动程序的引用或会话,并在交互完成后关闭句柄。
选择管理硬件引用的方法
在大多数情况下,您将在多个测试步骤中与同一硬件进行通信。为了避免在每个代码模块中打开和关闭仪器会话对性能的影响,请务必考虑如何在测试序列中管理硬件引用。 管理硬件引用的常用方法有两种:
• 通过从代码模块调用初始化和关闭函数来手动管理硬件引用。
• 使用会话管理器自动管理硬件引用生命周期。
如果使用的是仪器驱动程序,或者使用VISA或IVI驱动程序直接与仪器通信,除非特别需要直接控制硬件会话生命周期,否则请使用会话管理器。 如果使用的是DAQmx等硬件驱动程序,则不能使用会话管理器,必须手动管理引用。
使用TestStand变量手动管理硬件引用
初始化仪器时,将会话引用作为输出参数传递给调用序列,然后将引用存储在变量中。 然后,可将变量作为输入传递到需要访问仪器的各个步骤。
包括NI-DAQmx和VISA在内的许多驱动程序以及大多数仪器驱动程序都使用I/O引用数据类型来存储会话引用。在TestStand中使用LabviewIOControl数据类型来存储这些引用。
使用LabVIEWIOControl类型的变量在代码模块之间传递硬件引用,例如DAQ任务引用
在TestStand和代码模块之间显式传递仪器句柄时,请将硬件引用存储在局部变量中。 如果硬件在多个序列中使用,请将句柄作为序列参数传递给有需求的每个序列。 避免使用全局变量存储硬件引用,因为可能难以确保仪器已在使用引用之前完成初始化。
请使用“设置”步骤组初始化硬件,并使用“清理”步骤组关闭硬件引用,具体原因如下:
• 由于清除步骤组总是在执行终止时运行,因此如果用户终止序列执行,硬件引用仍将关闭。
• 由于设置和清除步骤组会在所选步骤之前和之后执行,因此可交互地执行使用硬件引用的步骤。
使用“设置”和“清理”组初始化和关闭硬件引用
使用会话管理器自动管理硬件引用
对于VISA和IVI仪器句柄,可使用会话管理器自动管理硬件引用。使用会话管理器具有诸多优势,包括:
• 减少耦合 — 不必在软件组件之间传递仪器句柄变量。每个组件都会指定一个逻辑仪器名称来获取会话。
• 减少编程语言障碍 — 用不同语言编写的代码模块可以使用同一会话,而无需传递可能难以在各种语言之间转换的句柄。
• 生命周期控制 — 由于仪器会话是具有引用计数的ActiveX对象,因此可将会话的生命周期与ActiveX引用变量的生命周期相关联,而无需使用支持ActiveX引用变量的语言显式关闭仪器。
会话管理器会在创建会话后自动初始化相应句柄,并在针对此会话的最后一个引用释放后自动关闭相应句柄。代码模块和序列会传递一个逻辑名称(例如“DMM1”),用于从会话管理器中获取会话对象,该对象包含相应的仪器句柄。
使用会话管理器时,请将会话对象存储在TestStand对象引用变量中。 由于会话生命周期与对象引用变量的生命周期相关联,因此无论有多少序列代码模块和子序列访问同一会话,每次执行时仪器句柄都会初始化和关闭一次。
在以下示例中,“获取DMM会话”步骤使用逻辑名称获取针对DMM仪器会话对象的引用。此步骤将会话引用存储在局部变量中,使会话在序列执行期间保持初始化状态。
借助会话管理器,可使用逻辑名称引用仪器。 会话管理器VI使用逻辑名称获取DMM IO引用
关于如何使用会话管理器的更多信息,请参阅
上一个示例序列从调用会话管理器的LabVIEW代码模块中获取会话,而不是直接调用会话管理器,因为该示例将LabVIEW适配器配置为在单独的进程中运行VI。 关于如何使用会话管理器的更多信息,请参阅
调用硬件驱动程序库
要与任意类型的硬件通信,需要使用驱动程序库,该库提供的一系列功能有助于使用编程语言执行各种任务。 使用驱动程序库时,通常会调用多个VI或函数来执行单个逻辑操作,例如进行测量或配置触发器。 创建代码模块来实现此功能,而不是直接在TestStand步骤中调用库函数,此方法具有以下优势:
• 避免了针对各函数的步骤功能产生的开销
• 提供了驱动程序调用和TestStand序列之间的抽象层
• 更易于跨测试程序共享实现过程