探测故障的最佳时机是在开发过程的早期。如果使用统一建模语言(UML),甚至在分析和设计期间就可以发现故障。然而,软件的集成和测试十分困难,嵌入式系统更困难,由于输入和输出少,系统的可操作性和可见性都很有限。反常的系统状态尤其难以测试,因为在确定系统在某一状态下的行为前,必须使系统进入该状态。
本文提出将测试仪器(instrumentation)代码注入UML模型实现中的观点,目的是提升系统的可控性、可观察性和易测性。测试仪器可应用在开发和目标环境中,并可在模型级进行交互式系统调试。在批处理模式下,测试仪器是数据采集、初始化和测试自动化的基础。本文旨在:简要介绍基于模型的软件工程以及这些模型的实现;概述基于模型的软件的集成测试方法;确定模型系统内重要的运行时间数据和执行关键点;阐述在运行时间采集和操作模型数据的几种方案;使测试仪器能自动进行测试。
软件故障是指程序中的错误指令或计算,软件故障的执行将导致软件状态出错。当错误传到输出,并作为一个异常结果呈现在系统外时,故障就会发生。程序的可控性是指一套测试系统强迫被测程序遵循一个特定执行路径的能力,也有可能沿这条路径的执行出错。程序的可观察性是指这套测试系统发现错误状态继而指出故障所在的能力。
系统的内部状态对于确定测试的正确性至关重要。系统的输出是由系统的初始状态及其输入决定的。初始状态不同的系统,即便输入相同,输出也会不同。系统的最终状态也必须作为评估测试正确性的一部分予以考虑,因为不正确的内部状态最终会传到系统的输出,并导致错误。系统的复杂性也使得预测系统的正确输出变得愈加困难。
初始状态+输入--->最终状态+输出
在“黑匣子”测试方法中,只有系统的外部输入和输出可知。需要用一个特殊的测试激励序列将错误传给输出,以便区分错误和正确的程序。所需的特殊序列越长,程序的可测性就越小。与“黑匣子”相似,嵌入式系统的可控性和可观察性也较低。评估最终系统内部状态的结果能缩短检测误差所需的特殊输入序列,从而产生更小、更易处理的测试案例。测试仪器力求同时提高软件程序的可控性和可观察性,以获得更具可测性的程序。
在应用代码中使用测试支持仪器的技术是一种“玻璃匣”测试方法。在开发系统的UML模型时,开发者必须了解系统将要完成的任务。基于测试仪器的错误隔离策略可以将UML模型的知识运用于集成测试。系统的操作和状态在分析级比在编码级更具可见性,因为后者受到实现细节的影响。
仅从外部输入设置测试的初始系统状态需要特定的外部激励序列。异常状态下的系统操作是很多嵌入式应用中验证的关键,但生成这些初始状态并不简单。本文所描述的技术可利用测试手段,大大提高可控性和可观察性。
集成测试的步骤
集成测试可分成两个重要阶段,即动态验证和目标集成。动态验证是在开发环境下运行UML模型,其目的在于确定模型的正确性。目标集成涉及到在目标环境中集成软件和硬件。动态验证和目标集成两者都是在分析级上进行的,均使用同样的工具,即测试支持仪器。
要尽可能多地进行动态验证测试,其原因有很多:硬件的可用性、硬件/软件的分离、更短的调试周期,以及工具的使用。如果在动态验证的运行测试后,可以确信模型没有问题,目标集成的调试就可以集中在系统组件之间的接口上,或特定平台问题上。
a. 用UML建立嵌入式系统模型
将UML模型有效地用于嵌入式应用的软件工程,要求开发进程能确保:模型是严格而完整的;在不影响模型的情况下优化所生成的系统实现;系统的整体结构由进程通过多个版本及要求的升级来维持。
为达到这些目标,基于模型的软件工程采用一种转换方法,重点讨论采用这种转换方法在代码中添加测试支持,但该技术也可应用于手工实现的UML模型。这种转换方法的特点将在下文介绍。
b. 分析模型
分析是针对问题本身为其建立与实现无关的模型方案的过程。有效的分析模型是严密而完整的,而且与实现方法无关。UML是由OMG定义的一种标准符号,主要用于表达分析建模。分析过程可以产生:
域(domain)模型:这是一种UML类图,它将系统分解成独立的主题区域,称为域。这些域由包和从属箭头显示桥表示,其中后者是域之间的需求流(flow of requirement)。可以对域进行分析,或者用其它方法开发,如人工编写的代码、继承代码、从其它源生成、从某个库导入等等。域服务是组成域接口的方法。由于域为某个问题区定义了完整的规范,所以可以独立对其测试,然后再与其它域结合以便进一步测试。
信息模型:对于每一个要分析的域来说,UML类图可用于定义组成该域结构的类(class)。类之间互相关联,还可继承其它类。
情境(scenario)模型:UML序列表或UML协作图捕获某个特定域的主要情境,用于表现域服务(操作)、类服务(方法)、类事件消息及该域引用的域外服务之间的相互作用。
状态模型:对于接受事件消息的每一个类来说,UML状态图可用于捕捉类周期,并定义该类与状态有关的特性。
行为模型:对于每个域服务、类服务和状态行为,都会生成一个详细而明确的行为描述。这由一种行为语言来表达,这种分析级“编程”语言提供了完整的分析级执行基元,而不会影响实现。用行为语言来表示行为细节,可以在实现分析基元的转换阶段之前保留极大的自由度,这对于优化至关重要。
c. 设计
设计是产生可将分析构造映射到运行时间环境中的策略和机制的过程。其概念与分析不同,大部分初步设计工作可以在与分析活动无关的情况下进行。
d. 转换
转换是用设计策略将每一个要分析域的UML模型映射到实现的过程。设计分两个阶段进行:
结构设计:识别系统的执行单元(线程/任务/进程),将其分配至处理器,并将域分配至单元。
机械设计:开发将分析映射到实现的详细模式(用模板描述),并建立基本机制以支持这一实现。