使用TestStand创建测试程序时,核心测试功能在单独的代码模块中实现。TestStand提供的适配器可调用使用各种编程环境和语言(例如LabVIEW、LabWindows™/CVI™、C#、VB .NET、C/C++和ActiveX)开发的代码模块。
本文档讨论了开发测试系统代码模块并从测试序列中调用这些模块时可能会用到的最佳实践。要使用本文档,您需要掌握TestStand的基本工作原理,其中包括创建基本测试序列的方法。如果您不熟悉这些概念,请在使用本文档之前了解以下入门资源:
开始开发测试系统之前,请考虑为测试系统的以下方面确定一种通用方法:
在设计测试系统时,请务必为代码模块定义一致的粒度级别。 粒度是指测试系统中每个代码模块的功能范围。 粒度低的测试序列调用的代码模块数量少,每个代码模块执行的功能更多;而粒度高的测试序列调用的代码模块数量多,每个代码模块执行的功能更少。
低粒度 | 高粒度 |
|
|
由于二者各有优势,因此您应该在这些极端情况之间取得平衡。
利用不同的粒度级别实现简单测试
为了在整个测试系统中保持一致的粒度,需要为代码模块开发创建一组标准,例如:
指定测试步骤中代码模块的路径时,可选择使用绝对路径或相对路径。建议不要使用绝对路径,原因如下:
指定相对路径时,TestStand会使用搜索目录列表来解析路径。 这些搜索目录通常包含当前序列文件目录、TestStand特定目录和系统目录。
在开始开发之前,请务必为测试序列和代码模块定义文件结构。 请按照以下指南定义用于存储序列文件和代码模块的策略。
定义目录结构,其中代码模块位于序列文件的子目录中
使用TestStand部署工具部署测试代码时,可为序列文件和相关代码模块选择特定的目标。 如果序列文件和代码模块的目标目录之间存在相对路径,则TestStand部署工具会更新序列文件中的路径来指向更新后的位置。 在大多数情况下,最好使部署的目录结构与开发系统上的目录结构相匹配,从而确保部署与开发计算机上的代码尽可能相似。
在定义测试系统的代码模块范围时,请务必定义在代码模块和序列文件中实现功能的策略。以下部分可帮助确定最适合实现常用功能的位置:
理想情况下,代码模块应包含与获得测试测量值直接相关的功能,并且测试序列应处理原始测试结果。这种方法具有以下优势:
为了简化测量,代码模块可以将原始测量值返回到序列中进行处理。例如,如果测试步骤用于测量待测设备(UUT)特定引脚上的电压,则代码模块应返回测量值,而不是直接在代码模块中执行检查。 您可以使用数值边界测试步骤来处理该值,从而确定序列文件中的测试结果。
在测试步骤中评估极限可简化代码模块并改进结果记录
但是,由于某些测试十分复杂,因此并非总是能够在序列文件中处理原始测试结果。 对于更复杂的测量,可能需要对结果数据进行进一步处理。 复杂数据可处理为单个字符串或数值结果,然后便可以在TestStand中使用字符串或数值比较功能对其进行评估。 例如,扫频测试的结果很复杂,无法直接进行评估,但可以将这些数据处理为代表最小值的单个数字。 在这种情况下,代码模块应评估处理后的结果,并以单独的参数形式返回频率数据,以便记录,如下面的移动设备测试示例所示:
对于更复杂的数据,请在代码模块中处理数据,生成数值或字符串结果,然后使用参数传出原始数据,以便记录
如果原始数据非常大,则将数据传递给TestStand的过程可能会对性能产生重大影响。 在这种情况下,可考虑将数据直接记录到TDMS文件中,并在测试报告中添加指向该文件的链接。 如此一来,可在无需将数据传递给TestStand的情况下从报告中引用数据。 关于此方法的更多信息,请参阅在报告中添加超链接 — TDMS文件。
如果该步骤无法使用测试步骤中可用的评估类型来确定测试结果,请考虑创建具有附加功能的新步骤类型来处理所需的测试类型。 关于创建自定义步骤类型的更多信息,请参阅本系列中的自定义步骤类型开发的最佳实践一文。
对于许多测试而言,UUT或测试环境必须处于特定状态下才能执行测试。 例如,进行温度测量可能需要使用激励电压,或者必须将加热室设置为指定温度。 对于这些类型的模块,应使用参数来传递输入值,例如激励电压或所需温度。 与上一部分所述的直接在代码中处理极限相比,这与在测试代码模块中返回原始数据具有许多相同的优势。
TestStand具有使用测试步骤的结果来生成报告和记录数据库的内置功能。因此,请避免直接在代码模块内进行任何类型的数据记录。 相反,请确保将要记录的所有数据以参数形式传出,并使用TestStand记录相应数据。 一些数据(例如测试结果、极限和错误信息)将自动记录。 要记录其他数据,可使用其他结果功能来指定要添加到报告中的其他参数。
关于将结果添加到测试报告中的更多信息,请参阅TestStand随附的将自定义数据添加到报告示例。
如果您对记录有特定要求,请考虑修改或创建结果处理插件。如此一来,可以使用内置的TestStand结果收集功能收集结果,同时可以确定结果的处理方式和显示方式。如需更多信息,请参阅TestStand过程模型开发和自定义的最佳实践文档的“创建插件”部分
由于实现循环的每种方法都有其自身的优缺点,因此很难确定理想的方法。请使用以下指南来帮助确定最适合您的应用程序的策略:
在代码模块中进行内部循环
在序列文件中进行外部循环
许多测试系统可借助开关功能使用单个硬件测试多个待测区。 借助预定义的路由,开关功能支持以编程方式控制连接到特定硬件的待测设备(UUT)引脚。
您可以通过以下方式在TestStand代码模块中实现开关功能:
使用NI Switch硬件时,NI Switch Executive可用于快速定义路由。在可以访问NI Switch Executive的情况下,使用内置的步骤设置来实现开关功能通常是最佳方法,此方法具有以下优势:
使用NI Switch Executive直接在TestStand步骤设置中指定路由,包括TestStand表达式支持,从而使用当前循环索引或其他属性来动态确定路由
为了避免为较简单的任务维护代码模块,可使用TestStand中的表达式语言来执行基本计算和单维数组操作。由于编程语言提供了更适合这些任务且更强大的功能,因此应在代码模块中执行更高级的编程要求。 例如,与使用表达式语言相比,使用原生LabVIEW创建数组函数可更轻松地完成多维数组的连接。
在某些情况下,可使用.NET框架随附的原生类避免创建过于复杂的表达式。 例如,System.IO.Path类可在无需创建代码模块的情况下用于快速执行路径操作。
无需代码模块的参与,即可借助.NET步骤来使用.NET框架方法
实现代码模块时,许多设计决策都会影响创建的许多代码模块。 本部分提供有关以下概念的指南:
访问代码模块中的TestStand数据的方法有两种:
在大多数情况下,与使用TestStand API直接访问数据相比,使用参数传递数据是一种更好的方法,具体原因如下:
尽可能使用参数将所需数据传递到代码模块
但是,根据步骤的状态,当代码模块需要动态访问各种数据时,使用API直接访问属性可能很有帮助。 在这种情况下,使用步骤参数会导致参数过多,而在不同情况下,实际用到的参数只有一部分。
如果要在代码模块中使用TestStand API,则需要向SequenceContext对象(ThisContext)传递一个引用作为参数。SequenceContext对象可访问所有其他TestStand对象,包括TestStand引擎和当前的Runstate。如果使用终止监视器或模态对话框VI,则需要序列上下文引用。
使用SequenceContext访问代码模块中的TestStand API,可用于以编程方式访问数据
如果要在TestStand之外复用代码模块,请记住,只有在从TestStand序列调用相应模块的情况下,才能使用TestStand API进行操作。模块通过API从TestStand获取的所有数据将不可用。 从TestStand外部调用代码模块时,可先检查序列上下文引用是否为空,从而定义获取测试数据的备用机制。 在LabVIEW中,可以使用非法数字/路径/引用句柄?函数,它会返回一个布尔值,如图3所示。
对于在TestStand之外使用的代码模块,请使用非法数字/路径/引用句柄?检查SequenceContext对象引用的有效性
在许多情况下,代码模块会在测量或分析过程中生成大量复杂数据。 由于TestStand会在存储此类数据时创建数据副本,因此请避免将此类数据存储在TestStand变量中。 这些副本可能会降低Runtime性能和/或导致内存不足错误。 使用以下方法来管理大型数据集,即可避免创建不必要的副本:
用户按下“终止”按钮时,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系统性能的最佳实践。
代码模块的常见用途是与测试硬件连接以设置激励并进行测试测量。 与硬件通信的方法包括:
• 使用硬件驱动程序(例如NI-DAQmx)直接与硬件通信。
• 使用仪器驱动程序,此驱动程序可在内部通过VISA或IVI硬件驱动程序将命令发送到仪器。
所采用的通信方式取决于使用的硬件类型。 对于这两种通信,您都需要在进行特定驱动程序的调用之前打开针对驱动程序的引用或会话,并在交互完成后关闭句柄。
在大多数情况下,您将在多个测试步骤中与同一硬件进行通信。为了避免在每个代码模块中打开和关闭仪器会话对性能的影响,请务必考虑如何在测试序列中管理硬件引用。 管理硬件引用的常用方法有两种:
如果使用的是仪器驱动程序,或者使用VISA或IVI驱动程序直接与仪器通信,除非特别需要直接控制硬件会话生命周期,否则请使用会话管理器。 如果使用的是DAQmx等硬件驱动程序,则不能使用会话管理器,必须手动管理引用。
初始化仪器时,将会话引用作为输出参数传递给调用序列,然后将引用存储在变量中。 然后,可将变量作为输入传递到需要访问仪器的各个步骤。
包括NI-DAQmx和VISA在内的许多驱动程序以及大多数仪器驱动程序都使用I/O引用数据类型来存储会话引用。在TestStand中使用LabviewIOControl数据类型来存储这些引用。
使用LabVIEWIOControl类型的变量在代码模块之间传递硬件引用,例如DAQ任务引用
在TestStand和代码模块之间显式传递仪器句柄时,请将硬件引用存储在局部变量中。 如果硬件在多个序列中使用,请将句柄作为序列参数传递给有需求的每个序列。 避免使用全局变量存储硬件引用,因为可能难以确保仪器已在使用引用之前完成初始化。
请使用“设置”步骤组初始化硬件,并使用“清理”步骤组关闭硬件引用,具体原因如下:
使用设置和清理组初始化和关闭硬件引用
对于VISA和IVI仪器句柄,可使用会话管理器自动管理硬件引用。使用会话管理器具有诸多优势,包括:
会话管理器会在创建会话后自动初始化相应句柄,并在针对此会话的最后一个引用释放后自动关闭相应句柄。代码模块和序列会传递一个逻辑名称(例如“DMM1”),用于从会话管理器中获取会话对象,该对象包含相应的仪器句柄。
使用会话管理器时,请将会话对象存储在TestStand对象引用变量中。 由于会话生命周期与对象引用变量的生命周期相关联,因此无论有多少序列代码模块和子序列访问同一会话,每次执行时仪器句柄都会初始化和关闭一次。
在以下示例中,“获取DMM会话”步骤使用逻辑名称获取针对DMM仪器会话对象的引用。此步骤将会话引用存储在局部变量中,使会话在序列执行期间保持初始化状态。
借助会话管理器,可使用逻辑名称引用仪器。 会话管理器VI使用逻辑名称获取DMM IO引用
关于如何使用会话管理器的更多信息,请参阅<Program Files>\National Instruments\Shared\Session Manager中的NI会话管理器帮助。
上一个示例序列从调用会话管理器的LabVIEW代码模块中获取会话,而不是直接调用会话管理器,因为该示例将LabVIEW适配器配置为在单独的进程中运行VI。 关于如何使用会话管理器的更多信息,请参阅<Program Files>\National Instruments\Shared\Session Manager中的NI会话管理器帮助。
要与任意类型的硬件通信,需要使用驱动程序库,该库提供的一系列功能有助于使用编程语言执行各种任务。 使用驱动程序库时,通常会调用多个VI或函数来执行单个逻辑操作,例如进行测量或配置触发器。 创建代码模块来实现此功能,而不是直接在TestStand步骤中调用库函数,此方法具有以下优势: