查看原文
其他

【科学探索奖获得者 谢涛教授 演讲全文】智能化测试:软件自动化的先行者

谢涛 软件质量报道 2023-06-13


【作者介绍:谢涛,北京大学计算机科学技术系讲席教授,高可信软件技术教育部重点实验室(北京大学)副主任,美国科学促进会(AAAS)会士,电气电子工程师学会(IEEE)会士,美国计算机协会(ACM)杰出科学家,中国计算机学会(CCF)杰出会员。曾获国家自然科学基金委海外杰出青年科学基金以及其延续资助,科学探索奖,美国NSF Faculty CAREER Award,IEEE计算机协会软件工程技术委员会(TCSE)杰出服务奖等。担任CCF软件工程专委会副主任,CCF-IEEE CS青年科学家奖评奖分委员会主席,中国计算机大会(CNCC 2020)程序委员会主席,软件工程旗舰国际会议ICSE 2021程序委员会共同主席,《软件测试、验证与可靠性(STVR)》Wiley期刊联合主编等。主要研究领域包括软件工程,系统软件,软件安全。】


感谢大会组委会邀请我来跟大家分享一下智能化测试。学术界和产业界在过去这几年都已经开始密切关注这一个话题了。


这个话题是在软件工程和人工智能之间的一个交叉话题。人工智能和软件工程交叉的一个方向是在上图的上方所示:智能软件工程(准确地说是智能化软件工程),这个名称里“软件工程”是连在一块的。在这个方向上,待解决的是软件工程里面的问题、任务,探讨如何让解决任务、解决问题的时候更加智能化,让其效率、效果更好。智能化软件测试是探讨智能化技术去辅助解决软件工程任务里面的测试任务。

另外一个方向是在上图的下方所示:智能软件工程,这个名称里“智能软件”是连在一块的。这个方向着眼于软件工程技术和方法来帮助更好地解决人工智能软件的问题,前面朱老师也提到了不确定性等人工智能软件里面的独有挑战。


IEEE Software 是面向全球软件从业人员的杂志。在2020年,我作为这个杂志的客座编辑共同编辑和组织了一期软件工程和人工智能交叉的专辑。这一专辑收到很多投稿,最后专辑录用了几篇精选文章,外加来自于相关专家们的观点综述,涵盖了学术界和产业界的知名专家,比如李开复博士等。


在2018年,国内也组织了主题为“大数据时代,软件自动化的机遇和挑战”的雁栖湖会议。

回到报告题目——智能化测试是软件自动化的先行者那什么是软件自动化?其实软件自动化在国内很早就已经有所推动了,比如南京大学徐家福先生和吕建院士的团队早期在这个方面做了很多工作。在国际上也有不少工作(有时也叫做自动编程“automatic programming”),特别是在近一二十年。软件自动化指的是创建软件不需要人去写代码了,只要提供需求(需求可能是形式化的,也可能是非形式化的——用自然语言描述的)。也就是说,用户只要告诉软件自动化系统需要满足什么样需求的软件,那系统就自动把这个软件给生成出来了,不需要用户自己去写软件。在这个报告里,我把这个定义放得更宽泛一点,把它引入到软件测试,那自动生成的软件就是用于进行软件测试的软件。

   

 让我们聚焦到软件测试,软件测试需要的人力开销大,而且效果不是那么好。软件测试里面有三大任务,一个是测试数据的生成,二是测试预言(test oracle)的生成,三是测试数据和测试预言的执行(测试数据和测试预言合在一块叫测试用例)。这三大块都需要花不少的人力,迫切需要用自动化、智能化的手段去降低开销,提升效果。 

为了更好地理解智能化测试,这里我借助了来自于其它领域的人工智能学者在谈智能化时提的三个概念,挪到智能化测试来提也是适用的。第一层是人机协作,去做前面提到的测试三大任务,还是人来做,同时在旁有工具做支撑。第二层是自动化,不需要人了,完全是工具在做,但是工具是比较笨的:人事先编好了规则让它做什么,工具才做什么,它自己不能自主主导该做什么或者该怎么做。第三层是智能化,是说规则不是定死的,不是人给定的,而是从数据里面去学出来的。而且随着数据的增多和演化,它也会不停地自学习,这样当越来越多地去用这个工具的时候,工具的使用就会产生数据,并且工具从这些数据中进行自学习后就会变得越来越强大、聪明。      

 下面用测试用例执行的场景去阐述这三者的区别

 大家去做网站或应用系统测试的时候,可以去写出如下图左侧所示的自然语言测试用例,然后测试人员去读这个测试用例里包含的5个测试步骤,来去手动地执行这些测试步骤。这里是人去读自然语言,然后去测被测软件,主要还是人在做。去人机协作的话,可以加进一些工具支撑,比如通过简单工具支持,在自然语言句子里加亮突出需要测试人员手动输入的值,使得测试人员的阅读效率更高一些。


测试自动化就是人去把上图左侧所示的自然语言测试步骤用编程语言写成测试脚本(如上图右侧所示),这样当被测软件有变更之后要再去测试时(也就是回归测试时),就没有必要人一步一步再去读测试步骤来去操作被测软件,而是用测试自动化的框架去自动运行之前人写下的测试脚本。

那什么是智能化呢?这和近十年前就提出来的一个方法相关,叫自动化测试自动化,是指把自然语言测试步骤自动地翻译成对应的测试脚本,不需要人来参与到这个翻译的过程。  

国际软件工程大会(ICSE)是国际软件工程研究界的年度盛会(我担任ICSE 2021的程序委员会共同主席)。在ICSE 2012上有这么一篇论文(“Automating Test Automation”),描述了一个实现“自动化测试自动化”的系统,是我之前的博士生Suresh Thummalapenta2010年博士毕业后加入IBM印度研究院做的工作。这个系统用自然语言处理外加一些程序分析技术对自然语言测试步骤进行分析,最后能够自动翻译出对应的测试脚本。这个系统部署于IBM 测试服务团队,为其节省了很多测试人力开销 。


最近我们和微信测试团队合作的一个研究工作(在一篇ESEC/FSE 2020 Industry Paper中有介绍)也包含有智能化的成分。在准备一个测试用例去测微信的时候,人去写测试用例所包含的自然语言测试步骤,然后对每个测试步骤人再去写一个API实现(测试脚本代码)来支撑这个测试步骤的执行。那大量的测试用例里就会有很多在自然语言描述上可能稍微不同但具有相同语义的测试步骤,这些测试步骤对应着同一API实现。我们这个工作是把自然语言测试步骤进行自动聚类,使得人不用花费重复劳动去写API实现来支撑那些有相同语义的测试步骤。这个工作部署于微信测试实践中来降低人力开销。


回到测试的另外两大任务——测试数据生成和测试预言生成。一个可以对这些任务去做支撑的系统是aiXcoder。它是我们北京大学李戈教授带领团队去研发和发布的系统,其2.0版本上线一个月就国际下载量超过13万。感兴趣的工程师可以去对其进行试用。它是本地安装,工程师所编写的代码不需要上传到网上,只需要本地部署的深度学习模型去做代码补全和代码搜索。除了代码补全和代码搜索,它还可以基于工程师写代码过程的行为(“指尖行为”)来支撑开发效能管理。

 “代码补全” 是指在写代码过程当中给工程师准确地去推荐其可能要写的代码,这个相比IDE现有的补全功能从准确性和补全长度上都有很大的进步。将来不光是工程师在写产品代码还是测试代码时,aiXcoder可以用深度学习去学公司内部代码或开源代码,来推荐给工程师如何去写产品或测试代码

      


第二大功能是代码搜索。aiXcoder可以从公司内部代码或开源代码中筛选出高质量的代码来让工程师去搜索来复用、适配或学习。工程师可以用自然语言、API方法名,甚至代码片段作为搜索关键字。另外,Stack Overflow上的代码或帖子也可以在搜索范围之内。虽然一般工程师去搜索的是产品代码,工程师也可以去搜索测试代码。例如,工程师可以去拿一个已经写好的一个或一组测试用例作为搜索关键字,去搜索出公司里其他人或自己之前写过比较类似的测试用例,这些测试用例的“兄弟姐妹”测试用例(即在同一测试类下的其它测试用例)就可以被借用过来去让工程师复用、适配或学习。

最后,aiXcoder根据记录在案每个开发、测试人员指尖上的敲键,来支撑开发效能管理。比如,可以知道工程师修补的某个缺陷原先是哪个工程师引入的,可以细粒度地度量工程师的代码质量和产出量,辅助工程师去提升自身能力,也可以给管理者提供辅助。

 过去10多年也涌现不少针对测试数据生成任务的自动化、智能化工作。2005年开始在学术界兴起的一个技术是动态符号执行,其是扩展了70年代提出的(以静态程序分析来实现的)符号执行来用动态程序分析的方式去实现。以下图左方所示被测代码为例,为了把最后一行代码覆盖到来抛出异常,需要提供一个至少有一个元素的整型数组,而且第一个元素等于一个特别大的数:1234567890。测试数据随机生成技术是很难随机地去生成这样的测试数据。


动态符号执行是以随机生成的一个输入数据或者缺省输入数据作为出发点。对于整型数组输入,缺省输入数据设定的是空指针。在执行给定的输入的时候,动态符号执行监控它执行时经过了哪个路径(哪些分支),然后把经过这个路径的所有输入值需要满足的约束(称之为路径条件)收集起来。为了生成下一个输入数据,动态符号执行选取一个之前执行遍历过的路径,然后选取这个路径上的一个分支,称之为B,把B上收集的约束进行取反,再把这个取反后的约束和这个路径上B之前所有分支上收集的约束进行逻辑AND起来。在上图所示的例子中,情况比较简单,每次只需要选取上一次遍历的路径,并选取这个路径上的最后一个分支来取反。

比如说最开始执行了空指针的输入值,在第一个条件语句的真分支就返回了,所以收集到的路径条件是:a == null (a等于空指针)。然后动态符号执行对a == null进行取反,得到a != null约束,利用一个约束求解器(比如微软研究院研发的Z3)来对这个约束进行求解,得到一个不等于空指针的最简单的数组:一个空数组里没有任何元素,但是它不是空指针。执行这个新的输入值到达了第二个条件语句,并覆盖了其假分支,收集到的约束是!(a.Length > 0)。接着动态符号执行对这个约束进行取反,得到a.Length > 0约束,对其求解后得到一个数组,其只包含了一个值为0的元素。执行这个数组到达了最后一个条件语句,并覆盖了其假分支,收集到的约束是!(a[0] == 1234567890)。接着动态符号执行对这个约束进行取反,得到a[0] == 1234567890约束,对其求解后得到一个数组,其只包含一个值为1234567890的元素。这样,最后执行这个数组就能去覆盖最后一个条件语句的真分支,造成抛出异常。经过如上重复生成测试数据的过程,动态符号执行把这个很小被测软件中所有的分支和路径都遍历了一次,从而获取了高(全)分支覆盖率。这个技术看起来挺有效,但是在实践中用起来会碰到挺多挑战。比如一个被测软件可能不长,只有平行的10个条件语句,假如每个条件语句的两个分支所有组合形成的路径都是可以走得通的,那总共要去遍历的路径数就有2的10次方,遍历空间太大了,造成“路径爆炸”挑战,阻碍了动态符号执行去高效率地达到高覆盖率或缺陷检测能力。     

以应对动态符号执行在实践中碰到的挑战,我们和微软研究院合作研发了一个叫Pex的工具,来自于2007年正式开始并持续近10年之久的一个项目。其中,在2008年暑假我作为微软研究院访问研究员去为Pex设计和实现一个叫Fitnex的算法(发表在DSN 2009会议上),旨在缓解路径爆炸的挑战。这个成果成为Pex的缺省路径搜索策略。Pex在2015年起被微软发布为其Visual Studio企业版中的IntelliTest工具。当时虽然智能化测试还没有火起来,微软已经想到把这个智能化测试的工具名称用到它的产品中了。


测试预言生成这个方面,自动化、智能化的程度要低一些,因为工具去自动判定被测软件的预期行为是很有挑战的。目前主要是看人如何去贡献其智慧和力量来表述测试预言,并和自动产生测试数据的智能化技术一块配合协作。这样在去培养开发者测试技能的时候,对开发者设计出高质量测试数据的能力就要求不是那么高了,而培养开发者有强的能力去写出高质量的测试预言就变得越来越重要了。在开发者测试中,去写高质量测试预言的一个形式是带参数的单元测试用例(parameterized unit tests)。这是我在Pex项目的合作者他们在2005年就提出来的理念。

     

传统的单元测试用例是没有参数的,所写的单元测试用例本身就是可以直接运行,不需要传入参数值的。而带参数单元测试用例要参数值被提供后才能运行起来,除了手动提供参数值外,开发者也可以利用测试数据生成工具来自动生成这些参数值。这样就有两大任务,一个就是如何去产生高质量的参数值,这个可以用工具自动地去做,二是如何去表述高质量的测试预言(比如断言),这个可以人去做

在传统单元测试用例里,断言会写得特别具体,只针对在测试用例里固定写死的测试数据。而在带参数单元测试用例里,断言就要写得足够泛化,使得由参数传进来的大量测试数据都可以去适用于这个断言。当前不同的程序语言和不同的单元测试框架基本上都支持带参数单元测试用例,但是不见得所有框架都有测试数据生成工具来支撑自动产生参数值。没有工具支撑时,就需要人去定义可以作为参数值的数据集合。

比如,上图中部所列出的两个传统单元测试用例都是去测试栈。第一个CT1是往空的栈推一个值为1的元素,然后看这个栈的大小是不是变成1了。第二个CT2是把值为1的元素换成另外一个值30。没具体列出来的传统单元测试用例也包括往空的栈推两个元素(值分别是1和 30),然后看这个栈的大小是不是变成2了。去测这个栈人就要去写更多、更丰富的方法序列和方法参数值,可能最后就得写了一大堆比较冗余的测试用例。我们的FASE 2011会议论文提出了4个步骤来引导人去把传统单元测试用例变成带参数单元测试(如上图最右边所示的例子)。

首先是把常量、对象参数化,把传统单元测试用例里的常量和对象变成带参数单元测试的参数。在CT1和CT2里,调用构造函数创建的空栈以及要推入栈的元素值会被提升为成测试用例的参数。

其次是去泛化测试用例中的断言。把CT1和CT2里的断言泛化为往栈里推一个元素后,栈的大小变成推此元素前的栈大小加1,而往栈里推两个元素后,栈的大小变成推此两元素前的栈大小加2。 

 然后去增加假定(assumptions)来过滤掉所传进来并不适用于测试用例内测试场景和断言的参数值数据。例如,如果往带参数单元测试用例传一个空指针的栈参数,很自然地会造成抛出异常。为了避免让测试数据生成工具去产生并传入一个空指针的栈参数,可以在这个带参数单元测试用例最前面加上一个假定,来指定栈参数不能为空指针。这样,当工具生成有违反此假定的测试数据时,就会对其自动过滤掉,不去继续执行测试用例的方法体。

最后是对已得到的多个带参数单元测试用例进行跨用例的泛化。比如,已有的几个带参数单元测试用例是把一个或两个元素推进栈,其实所关注的被测行为可以进一步泛化为当推进任意给定数目n个元素后,最后栈的大小是等于推这些n个元素之前的栈大小加n。这样,我们可以得到上图最右边所示的带参数单元测试用例来支撑推任何数目的元素进到栈里:传进去一个数组,然后把数组的元素一个一个往栈里推,然后用断言去表述很泛化的行为,把前面这些传统单元测试用例所表述的行为都包含进去了。


 为了评测和训练学生和工程师的编程和测试技能,基于像Pex这样的自动测试数据生成工具,我设计了一个人机协作训练模式:编程对决(Coding Duel)。编程对决成为微软研究院发布的有百万用户的Code Hunt和Pex4Fun编程游戏网站的主打游戏模式。

如下图所示,在学生(或工程师)去玩一个编程对决时,学生能看到的是一个空的或者错误的学生代码(如下图右上方所示),学生的目标是去猜出编程对决所包含秘密标准答案(如下图左上方所示)实现的功能需求是什么,然后再去修改所给的学生代码去实现这个需求。

 为了辅助学生去猜要实现的功能需求,编程对决平台通过Pex来给学生提供反馈。当学生点击界面上的“Capture Code(捕获代码)”按钮后,Pex就产生测试数据来尝试去区分开学生代码和标准答案的输入-输出功能行为。平台根据Pex产生的测试数据来返回一个表格(如下图下方所示)来列出给定哪些样本测试数据的时候,学生代码和标准答案的返回值是一样的,且是什么值,并列出给定哪些样本测试数据的时候,学生代码和标准答案的返回值是不一样的,且各是什么值。学生在这些样本测试数据和其返回值的启发下多次地去猜、去修改学生代码,以使得学生代码的行为达到和标准答案的行为一样(也就是说,Pex不能够产生任何测试数据来区分开两者的行为)。  

最近我们开始和五人科技公司开展合作,把上述训练模式结合到他们推出的「极客神灯」 (一个基于程序员远程协同开发的AI远程面试平台),去评测和训练广大学生以及工程师的开发和测试技能。我们也把上述训练模式进一步扩展,研发出更多更好的技术来自动出题、自动判题,以支撑工程师技能的智能化评测和培训。   


我们最近也开始和阿里巴巴的淘系质量部门合作,在他们RXT极测机器人测试平台上以计算机视觉的方式去对移动应用端进行测试,在这个平台上加入之前提到的持续自学习、强化学习的机制,把它的探索机制变得更加智能。除此之外,我也和其它几个公司,比如蚂蚁金服、还有本次大会的赞助商复深蓝,一块去推动智能化测试,进行更多的探索

谢谢大家!


推荐阅读:

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存