一种Windows应用程序的GUI自动化遍历方法
2018-11-30朱方祥顾乃杰
朱方祥 顾乃杰,3
1(中国科学技术大学计算机科学与技术学院 安徽 合肥 230027)2(安徽省计算与通信软件重点实验室 安徽 合肥 230027)3(中国科学技术大学先进技术研究院 安徽 合肥 230027)
0 引 言
互联网发展至今日,各式各样的软件产品已经渗透到人们生活的方方面面。用户和软件之间的交互主要依赖图形用户界面GUI(Graphical User Interface)。用户通过软件GUI触发一定的事件,GUI响应用户事件并且把结果返回给用户。在现代软件的开发过程中,开发者会编写大量GUI实现相关的代码,其所占的比重大约是整个软件代码量的60%[1-2]。软件GUI丰富和复杂的特性给软件测试带来非常多的不便[3-4]。
GUI测试可以分为人工测试和自动化测试,自动化测试相比人工测试能够节省很多的人力成本和时间成本。学术界和工业界对GUI自动化测试的研究非常多。其中一部分研究是基于脚本的自动化测试,通过运行脚本自动执行测试步骤。测试脚本生成手段包括人工编写测试脚本和录制测试脚本[5-6]。这两种方法的自动化程度有限,需要测试人员具有一定的测试经验,而且随着软件功能的增加和版本的迭代会给测试用例的维护带来不便,并且测试用例能涵盖的GUI数目也有限。另外一部分研究是测试用例的自动生成,这种方法一般通过算法构建软件的GUI模型,并在模型基础上自动化生成测试用例。其中经典的模型有事件流图[7-9]EFG(Event Flow Graph)、事件序列图[10]ESG(Event Sequence Graph)。在此基础上文献[11]提出使用有限状态机FSM(Finite State Machine)可以得到更精简的模型。在这些GUI模型的研究中,GUI覆盖率直接影响模型是否完整高效,研究表明纯手工测试遍历GUI的覆盖率一般只有30.08%[12]。文献[13]首次设计出一种GUI驱动软件的自动化测试框架,其系统地提出了GUI模型构建的方法。框架中JFCGUIRipper可以自动遍历基于JFC框架开发的应用,其采用窗口名称区分不同的界面,GUI覆盖率不高。文献[14]提出一种模拟用户行为动态探测Android应用程序GUI的方法,其采用图像对比区分不同的应用界面,虽然比较简单方便,但是不能有效地解决遍历过程中的回环问题。而文献[15]提出通过结合静态分析遍历Android应用程序GUI的方法可以一定程度提高GUI覆盖率,但是这种方法仅在Android操作系统上实现。上述的研究内容中,大部分都是针对移动端GUI自动化的研究。虽然Windows比移动端的操作系统(Andorid、iOS)成熟要早很多,但是在自动化测试领域,其发展却不如移动端。近年来鲜有关于Windows GUI自动化测试的相关研究。
本文的主要贡献是提出一种自动遍历Windows应用程序GUI的方法,该方法基于GUI事件触发和GUI状态迁移模型,解决GUI遍历过程中的回环、遍历不充分、效率低三个关键问题。通过相关实验验证,该方法可以得到较高的GUI覆盖率,具有一定的实用性,可以作为其他基于模型自动化测试研究的基础。
1 相关概念
1.1 Windows控件树结构
一个普通的Windows GUI界面是一个由若干GUI元素组成的树型视图。GUI元素包括窗口、按钮、标签、编辑框等。Windows控件树是一种系统展示Windows应用程序界面信息的分层结构,其基本数据结构类似于多叉树。对于控件树的定义如下:
1) 树中的节点代表单个GUI元素。
2) 节点信息包括GUI元素的各种属性(位置、名称等)。
3) 除叶子节点外,每个节点可以拥有任意数量的子节点。
4) 除根节点以外,每个节点只能拥有一个父亲节点。
如图1所示,左侧为普通的Windows窗口,窗口包括一个普通的标题栏、一个文本框和两个按钮。标题栏中包括最小化、最大化、关闭按钮。右侧为相应的控件树表示。
图1 控件树结构
这种控件树表现方式的优点是可以完整表现一个应用界面的层次和嵌套关系,并且可以方便地通过遍历控件树找到所需的GUI元素。
1.2 相关定义
一般来说,Windows软件是一个多窗口应用,其界面包含该应用程序的多个窗口。现对窗口状态定义如下:
定义1窗口状态是描述某一时刻应用软件某一个窗口内部情况的抽象概念,单独的一个窗口状态可以由{O,P,V}三元组来定义。其具体描述如下:
1)O={o1,…,on}代表在GUI状态中的所有GUI元素。
2)P={p1,…,pm}代表O中单个GUI元素的所有属性。
3)V={(v11,…,v1m),…,(vn1,…,vnm)}代表O中每个GUI元素的所有属性的值。
定义2GUI状态是描述某一时刻应用程序图形用户界面总体情况的抽象概念,单独的一个GUI状态可以由{wt,W}二元组来定义。
1)wt代表该GUI状态下最顶层活动窗口的窗口状态。
2)W代表窗口集合,集合包括该GUI状态下除了最顶层窗口的其他所有窗口状态。
定义4迁移事件(Transition Event):在某一固定时刻,造成GUIV状态迁移的用户事件称之为迁移事件,其包括可达事件和终止事件。
定义5可达事件(Reachability Event):在某一固定时刻,那些用于打开新窗口的事件称之为可达事件。
定义6终止事件(Termination Event):在某一固定时刻,那些用于关闭当前顶层窗口的事件称之为终止事件。
1.3 自动遍历方法难点
在没有用户事件响应时,应用软件的界面趋于稳定状态,只需要遍历控件树结构即可获得GUI元素,但这些GUI元素并不能覆盖软件中所有的GUI元素。一些GUI元素必须响应特定的用户事件才能显示并出现在控件树结构中。想要尽可能探测越多的GUI元素需要不断地触发GUI事件。
在实际实现基于GUI触发的遍历过程中会遇到若干难点。这里先简单描述这些难点,并阐明为什么要解决这些难点。
1.3.1 回环问题
如图2所示,在某一时刻的遍历过程中,窗口A中只有Button,触发Button后会打开新的窗口B,控件树更新。B中只有取消按钮,触发Cancel后会关闭窗口B,控件树更新。此时如果遍历程序无法判断窗口A已经遍历过,则会把窗口A当成新窗口继续遍历,遍历到窗口A的Button然后触发点击事件,最后导致遍历程序在窗口A和窗口B之间发生死循环,使得遍历无法继续进行。如何有效地识别遍历过的应用程序界面,成为解决回环问题的关键。
图2 回环问题
1.3.2 遍历不充分
遍历算法在遍历过程中存在一定概率会提前触发终止事件并关闭当前窗口,使得遍历算法无法遍历该窗口中的剩余元素。由于在遍历过程中无法提前预知触发的用户事件是否是终止事件,因此需要增加步骤回溯机制来解决这种因为提前触发终止事件而造成的遍历不充分问题。另外对于需要验证步骤的窗口(比如用户名、密码登录),如果没有事先在指定控件中输入正确信息,遍历程序无法打开对应的新窗口。这种情况同样会导致遍历不充分,针对这类问题需要在遍历过程中加入步骤引导。
1.3.3 遍历效率低
软件中存在一些与软件本身功能无关的窗口,比如选择文件窗口或者打开文件窗口。当遍历到类似于选择文件的窗口是,文件浏览框中触发文件选择事件会不断的出现新文件,导致窗口界面一直发生变化。遍历程序会认为每次操作打开了新的窗口继续遍历,显然一直遍历文件路径是无意义的,会导致遍历效率低下。
2 关键算法及说明
2.1 GUI状态迁移判断
如何精确判断遍历过程中应用程序的两个界面是否相同是解决回环问题的关键。由定义2可知,GUI状态刻画了某一时刻应用程序图形用户界面的总体情况,所以判断当前应用程序的界面是否遍历过等价于判断当前应用程序的GUI状态是否已经遍历过。
本文提出对应用程序的窗口集合进行对比判断GUI状态是否迁移,具体判断步骤如下:
1) 判断两个GUI状态下窗口集合W的个数是否相同。
2) 判断两个GUI状态下的顶层活动窗口wt内部控件树是否相同。
对比顶层活动窗口的控件树时,采用树的度遍历方法,对于控件树的每个节点只会对比控件的类型是否相同,对属性值不作比较。如果窗口状态个数和顶层窗口的控件树都相同,则判断两个GUI状态相同。这种方法可以更加细粒度地对比应用程序界面的变化,有效地判断GUI状态是否迁移。
2.2 基于GUI状态变迁的步骤回溯算法
为了解决遍历过程中,提前触发终止事件而造成遍历不充分的问题,需要增加遍历的步骤可回溯的功能。
算法1描述了具体的基于GUI状态变迁的步骤回溯算法。算法的输入是某一时刻的GUI状态,输出正确回退的GUI状态。这里GUI状态除了顶层窗口状态wt和其他窗口状态集合W之外还有四种属性:element、isDone、terminate、lastEvent。element代表遍历程序在该GUI状态下已经遍历到的控件节点,isDone代表当前GUI状态下是否已经完成遍历过程,terminate代表终止事件,lastEvent代表发生状态迁移前所触发的事件。GUI缓存(GUICache)类似于一个数组结构,缓存遍历过程中发生变迁的GUIState。事件列表(EventList)也是一个数组,其保存遍历过程的执行路径。EventList[i]是 GUICache[i-1]到GUICache[i]状态变迁所发生的事件。
2.3 GUI遍历算法
算法2描述了核心的遍历算法,其输入是启动的被测应用。整个遍历算法覆盖遍历过程的GUI状态表示、遍历方式、步骤回溯等关键问题。其中使用深度优先遍历控件树的主要原因是Windows应用窗口模态(modal)和非模态(modelless)的特性。模态窗口指的是当打开该窗口的时候用户的交互只能聚焦在此窗口内,而非模态则没有这样的限制。然而Windows大部分的窗口都是模态窗口,所以在遍历到新的模态窗口的时候无法对窗口外的控件触发事件,只能触发窗口内部控件的事件。这从根本上否决类似于广度优先的其他遍历策略,并且采用深度优先的方式更符合用户操作软件的习惯,能够有效地提升GUI探测的深度和广度。
2.4 样式过滤
影响遍历效率的一个重要因素是一直遍历被测软件主要功能无关的窗口界面。所以需要事先对一些无需遍历的窗口界面进行过滤。如果采用对窗口及窗口内部元素一一对比的方式会非常麻烦。针对此问题,考虑使用Windows中的样式值作为过滤窗口页面的标准。Windows中窗口或者控件的样式值包含了绘制风格等信息。比如是否可见、是否有水平滚动条,是否有最小化按钮等信息。通常来说,软件中同样功能类型的窗口绘制风格相同,样式值也相同。
在遍历程序开始之前,将需要过滤的样式值写入配置文件的样式黑名单中。在遍历算法遍历元素的每一步都与黑名单中的样式值进行对比,如果样式值存在,则不再遍历该元素及以该元素为根节点的子树中的元素。窗口或者控件的样式值可以事先通过微软提供的spy++工具[16]获得。通过这样简单的方式可以有效地防止遍历程序局限在某一不重要的软件界面处,从而提升遍历效率。
3 工具实现方法
3.1 工具实现
遍历工具总体方法的架构图如图3所示。主要分为四个模块:UI提取模块、遍历模块、UI驱动模块、缓存模块。
缓存模块主要存放一些模块之间公用的数据,包括GUI状态的缓存、任务缓存、配置信息等,使用XML文件作为缓存的载体。
UI提取模块获取应用程序当前GUI状态下的所有窗口集合以及顶层活动窗口的控件树结构信息,并把这些信息传递给遍历引擎。
遍历模块实现具体的遍历算法,判断GUI状态是否变迁,更新缓存模块中的缓存信息,并且在窗口控件树的基础上深度遍历GUI元素。在遍历到某一GUI元素需要触发事件时,则把元素的控件路径和所需要触发的事件信息交给UI驱动模块。
UI驱动模块主要用来对应用程序控件触发事件,从而模拟用户动作。通过简单调用驱动API便可以在应用程序上模拟用户动作。比如点击按钮、输入文本等操作。
根据上述架构,实现Windows UI自动化遍历的原型工具。该工具使用Python语言实现,运行在Windows 系统的PC平台上。UI提取模块和UI驱动模块主要基于Pywinauto开源工具[17]实现。Pywinauto是一套用于Windows GUI自动化的python库,其对Windows应用程序自动化测试相关的API进行了封装,可以支持WinForm、WPF、Qt、MFC等不同框架的Windows应用程序。对Pywinauto的相关实现进行修改和补充,实现控件树从内存到XML文件的转换,使用XML的层级结构正好可以映射控件树结构。控件树缓存以XML文件的形式存储,不需要保留在内存中。在UI驱动模块,调用Pywinauto中封装好的API可以方便地发送指定事件给应用程序的GUI并执行相关动作。
3.2 实 验
遍历时探测的GUI元素越多代表了越好的遍历效果,可以使用GUI覆盖率作为评估标准。
(1)
GUI覆盖率一般形式如式(1),其中GC代表GUI覆盖率,GT代表应用程序包含的GUI状态总数,GE代表遍历执行过程中探测到的GUI状态总数,比值越高代表遍历效果越好。但是在实际实验过程中,无法直接准确获得应用程序的GUI状态数目,如果使用人工统计的方式统计GUI状态数目效率会很低并且存在误差。为了尽可能得到准确的GUI覆盖率,采用基于简单抽样的方式来近似估计GUI覆盖率。
(2)
GUI平均近似覆盖率计算公式如式(2)所示。首先对整个被测软件的GUI元素进行多次简单抽样,每次抽样保证GUI元素分布均匀,不会出现多个GUI元素在单一窗口内部。AAC代表平均近似覆盖率,ni代表被测应用中第i次人工随机抽样选取的GUI元素的个数,并将其作为第i次抽样的样本。在遍历程序的过程中,每遍历到一个GUI元素都与样本中的元素进行对比,如果相同则记录。pi是遍历过程中出现在第i次抽样样本中GUI元素的个数,pi和ni的比值代表第i次抽样的近似覆盖率。总共采样k次取平均值,得到平均近似覆盖率。使用GUI平均近似覆盖率代替GUI覆盖率进行定量的实验评估更加方便高效。
实验工具运行在Windows 10、内存为8 GB的PC机上。被测应用包括TapTap、Notepad、MSPaint、TeamViewer和KeePass五款Windows应用程序。其中TapTap是我们自主研发的Windows应用程序,其内部总共包含132个窗口页面,在功能上实现了窗口之间的相互切换。
遍历程序运行过程针对文件选择、文件保存等软件功能无关的窗口进行个性化过滤。用户可以触发的事件包括左击、双击、拖动等简单事件。对于每个被测软件进行三次人工抽样最后得到平均近似覆盖率,结果如表1所示。
表1 实验结果
3.3 横向比对
为了进一步体现本文方法的优越性,以上述提及的4个被测应用作为测试数据,将其与文献[13-14]中的方法在平均近似覆盖率进行了性能比较。文献[13]基于窗口标题对比判断应用程序界面是否改变,文献[14]基于软件图像对比判断应用程序界面是否改变。对三种方法进行测试,得到平均近似覆盖率进行对比。结果如表2所示。
表2 不同方法的平均近似覆盖率比较 %
3.4 结果分析
TapTap软件设计内容简单、功能单一,其设计目的是对遍历程序的正确性进行测试。从表1可以看出,由于设计之初规避了回环问题和遍历不充分问题,所以测试TapTap软件的近似覆盖率可以达到100%。对其他的Windows程序进行测试,其平均近似覆盖率在57.8%~74.3%之间。从表2以看出,相对于窗口标题对比和基于软件图像对比判断GUI状态迁移的方法,本文提出的基于窗口集合对比判断界面是否变化的方法对于GUI覆盖率有平均21.8%的提升。仔细分析其原因可知,这主要是因为本文方法能够更加细粒度地对比应用程序界面的变化,有效地判断GUI状态是否迁移因此取得了更好的结果。
另外分析遍历过程,普通Windows应用软件程序无法达到覆盖率100%主要因素有两个方面:第一,遍历程序对复杂事件的支持不足,比如没有处理鼠标右键、滚轮键点击事件。第二,应用软件中有的窗口必须连续触发顺序事件才能打开,自动遍历算法没有考虑触发事件的顺序。
4 结 语
本文提出一种自动遍历Windows软件GUI的方法。该方法基于GUI状态变迁构建完整的GUI遍历算法,并且解决了遍历过程中的回环、遍历不充分、遍历效率低三个关键问题。实现相关工具,通过实验分析该方法在自主研发的应用软件中覆盖率为100%,保证方法的正确性。对于普通的Windows应用程序,该方法的GUI平均近似覆盖率在57.8%~74.3%之间。
下一步工作方向:继续优化本文的遍历工作,添加对更复杂事件的处理,并且支持验证步骤等功能。基于Windows的GUI遍历可以适合多种测试场景。比如可以结合性能监控,在无任何测试用例前提下,自动检测出被测应用的性能瓶颈;也可以基于遍历过程中GUI状态变迁和事件的关系,构造模型并自动组织测试用例。