-
-
[翻译]类似AFL软件实现的属性测试技术
-
发表于: 2017-11-23 15:27 3792
-
在上一篇文章中,我论证了基于属性的测试和模糊测试本质上是相同的实践技术方法,或者说两者拥有大量的共性。在本篇后续文章中,我想要更加深入地探讨一下这个观点:首先,我将快速介绍一下在使用传统基于属性的测试工具过程中,我所遭遇到的挫折和彷徨;然后,我将提出一种假设的用户体验来解决这些关切和问题,这种假设的灵感主要来源于现代模糊测试工具,特别是AFL和Google公司的OSS-Fuzz。
引言:测试套件所迫切需要的内容
一般来说,一个大型的软件工程可能需要多种测试套件,它们拥有不同的目标,以不同的频率运行于开发人员交互或参与过程的不同层次中。
然而,在我所参与过的大部分工程中,最为通用的测试套件模式是一个持续集成套件,它能够针对每一次改变(建议或合成)运行,并且在开发人员想要在开发过程中确认自己的工作时,他们可以交互地运行该套件的某个子集。
从各方面来说,这样一个测试套件首先且首要是一个回归测试套件。开发人员可能会以测试优先的“TDD(测试驱动开发)”样式来编写代码,或者编写符合外部规范(隐式或显式)的测试用例,但一旦代码编写成型,测试套件最重要的功能就是记录(以一种可执行+可检验的方式)代码的关键属性,这些属性在之后的变更中必须保持不变。在一个处于积极开发状态下的大型代码库中,这种回归测试将给予未来开发人员修改代码的能力,同时确保他们不会破坏原有代码。
从这样的测试套件中我们想要获取什么特性?这有很多项内容,但是在此我想要重点强调其中重要的两点。具体来说,测试套件应该是快速和可重现的。
速度
执行速度非常重要,因为快速的测试能够给予开发人员迅速的反馈。在大型代码库中,添加一个特性或者修复一处错误通常并不困难,而在不破坏任何现有行为的前提下添加一个特性或者修复一处错误才是困难的。由于需要测试向你提供关于属性的一线反馈信息,所以它们经常会成为你的开发反馈循环周期速率的关键限制因素。缓慢的测试困扰着开发人员,迫使他们在开发周期内诉诸于人工或专门测试,而不是依赖于测试套件
可重现性
为了可靠地提供开发人员所需的信心,测试结果必须是可重现的。针对这点,我的意思是一旦一次测试成功了,那么在某些有意义的改变发生之前它应该可靠地持续测试通过,并且如果一次测试失败了,应该能够很可靠很容易地重现错误。
以上两种特性对于一个可行的持续集成回归测试套件是必要的。一旦测试通过并且结果合并,它们一定不能随便失败;否则,随机失败的综合影响将使得永远保持构造工程“健康”变得不可能,并降低持续集成系统所反馈信息的价值。另一方面,如果一次测试在开发周期或者预合并推送过程中失败,那么它必须易于重现错误,以便开发人员快速地调试并修复问题,同时要确保该问题已被解决。
基于属性的测试短板在哪里
传统基于属性的测试不满足以上这些标准,我猜测这就是它不像以前那么有吸引力的原因之一。
·因为传统基于属性的测试需要在运行时生成随机输入数据,所以为了获得较好的测试覆盖率,用户通常需要耗费相当大数量的运行时间(我所见过的大部分系统默认采用IOO(输入/输出操作)之类的操作)。由于一个小例子的IOO运行时间都很容易达到人类时间尺度,大型系统的一个坏习惯是,结束那些具有很多代价高昂的依赖项、I/O需求或者其他减速因素的测试用例,并且IO执行减速因素以及“IO手动选择执行的用例”都是要付出的令人困扰的代价。
·因为基于属性的测试随机选择测试用例,因此它倾向于(通过设计)不确定性,而这破坏了上述的可重现特性。基于属性的测试套件的作者和追随者们通常将这点描述为一个特性,理直气壮地指出这个特性给予每一个测试用例发现异常错误的机会,能够持续提升用户的覆盖率。然而,这种不确定性无法为一个必须针对每一个开发人员的每一次提交运行测试的持续集成套件提供支持;持续集成系统的工作是证明缺乏(某种)回归属性,而不是完全的正确性。
很多基于属性的测试套件试图(可选地)通过支持一个确定的随机数发生器来获得确定性。这种方法很有用,但它留给用户的可能仍然是一个脆弱的测试套件,在套件中用户生成策略或测试用例的微小改变都会导致所考虑的测试用例集合的巨大变化。如果一个开发人员对其他测试中所见到的用例进行更改,向数据生成策略中添加一条分支以支持他们的新特性,那么他可能会得到海量的怪异错误。
我所需要的工作流
以上述动机作为背景知识,我想要展示一下我认为我所需要的脱胎自基于属性测试的工作流。基于我的经验判断,该流程设计能够有效的弥补许多现有工具之间的隔阂,并满足大规模回归测试套件的需求。该描述的灵感来源于综合考虑处理上述关切问题,以及利用业已广泛应用于模糊测试领域,尤其是afl和oss-fuzz工具中的技术。
硬编码用例
我想要通过编写强制传递一些类型的所有输入的函数,以基于属性的样式来编写测试用例。然而,我同样想要以文本、人类可读格式(理想情况下,例如源代码文本或JSON格式)向我的代码储存库提交测试用例列表,以便回顾和整合。
当以默认模式(例如,make test或者你的持续集成系统入口)运行时,测试工具应该仅仅运行这些用例。通过运行一个固定。小型的用例集合,我们获得上文所需求的速度和可靠性。
用例生成
到目前为止,我所描述的知识一个“表驱动测试”的轻量级版本,而不具备通常用于定义基于属性测试的生成性。由于我是表驱动测试的忠实拥趸,因此很明显自动化测试生成应该拥有一些特性,来提供基于并改善这类测试的功能。
为了弥补隔阂,我希望在提交之前,在大多情况下,上文所提到的输入列表应该由基于属性的框架来生成。在编写一个测试之后,我可能会运行proptest generate命令,来开始一个传统的基于属性测试生成并最小化的过程;所不同的是,任何失败的测试用例,以及其他测试用例的样本,将自动写入“examples”文件,用于回顾以及开发人员可能的提交行为。
覆盖范围指导的生成和反馈
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课