首页
社区
课程
招聘
[翻译]类似AFL软件实现的属性测试技术
2017-11-23 15:27 3391

[翻译]类似AFL软件实现的属性测试技术

2017-11-23 15:27
3391

        在上一篇文章中,我论证了基于属性的测试和模糊测试本质上是相同的实践技术方法,或者说两者拥有大量的共性。在本篇后续文章中,我想要更加深入地探讨一下这个观点:首先,我将快速介绍一下在使用传统基于属性的测试工具过程中,我所遭遇到的挫折和彷徨;然后,我将提出一种假设的用户体验来解决这些关切和问题,这种假设的灵感主要来源于现代模糊测试工具,特别是AFL和Google公司的OSS-Fuzz。


引言:测试套件所迫切需要的内容

        一般来说,一个大型的软件工程可能需要多种测试套件,它们拥有不同的目标,以不同的频率运行于开发人员交互或参与过程的不同层次中。

        然而,在我所参与过的大部分工程中,最为通用的测试套件模式是一个持续集成套件,它能够针对每一次改变(建议或合成)运行,并且在开发人员想要在开发过程中确认自己的工作时,他们可以交互地运行该套件的某个子集。

        从各方面来说,这样一个测试套件首先且首要是一个回归测试套件。开发人员可能会以测试优先的“TDD(测试驱动开发)”样式来编写代码,或者编写符合外部规范(隐式或显式)的测试用例,但一旦代码编写成型,测试套件最重要的功能就是记录(以一种可执行+可检验的方式)代码的关键属性,这些属性在之后的变更中必须保持不变。在一个处于积极开发状态下的大型代码库中,这种回归测试将给予未来开发人员修改代码的能力,同时确保他们不会破坏原有代码。

        从这样的测试套件中我们想要获取什么特性?这有很多项内容,但是在此我想要重点强调其中重要的两点。具体来说,测试套件应该是快速和可重现的。

        速度

        执行速度非常重要,因为快速的测试能够给予开发人员迅速的反馈。在大型代码库中,添加一个特性或者修复一处错误通常并不困难,而在不破坏任何现有行为的前提下添加一个特性或者修复一处错误才是困难的。由于需要测试向你提供关于属性的一线反馈信息,所以它们经常会成为你的开发反馈循环周期速率的关键限制因素。缓慢的测试困扰着开发人员,迫使他们在开发周期内诉诸于人工或专门测试,而不是依赖于测试套件

        可重现性

        为了可靠地提供开发人员所需的信心,测试结果必须是可重现的。针对这点,我的意思是一旦一次测试成功了,那么在某些有意义的改变发生之前它应该可靠地持续测试通过,并且如果一次测试失败了,应该能够很可靠很容易地重现错误。

        以上两种特性对于一个可行的持续集成回归测试套件是必要的。一旦测试通过并且结果合并,它们一定不能随便失败;否则,随机失败的综合影响将使得永远保持构造工程“健康”变得不可能,并降低持续集成系统所反馈信息的价值。另一方面,如果一次测试在开发周期或者预合并推送过程中失败,那么它必须易于重现错误,以便开发人员快速地调试并修复问题,同时要确保该问题已被解决。


基于属性的测试短板在哪里

        传统基于属性的测试不满足以上这些标准,我猜测这就是它不像以前那么有吸引力的原因之一。

        ·因为传统基于属性的测试需要在运行时生成随机输入数据,所以为了获得较好的测试覆盖率,用户通常需要耗费相当大数量的运行时间(我所见过的大部分系统默认采用IOO(输入/输出操作)之类的操作)。由于一个小例子的IOO运行时间都很容易达到人类时间尺度,大型系统的一个坏习惯是,结束那些具有很多代价高昂的依赖项、I/O需求或者其他减速因素的测试用例,并且IO执行减速因素以及“IO手动选择执行的用例”都是要付出的令人困扰的代价。

        ·因为基于属性的测试随机选择测试用例,因此它倾向于(通过设计)不确定性,而这破坏了上述的可重现特性。基于属性的测试套件的作者和追随者们通常将这点描述为一个特性,理直气壮地指出这个特性给予每一个测试用例发现异常错误的机会,能够持续提升用户的覆盖率。然而,这种不确定性无法为一个必须针对每一个开发人员的每一次提交运行测试的持续集成套件提供支持;持续集成系统的工作是证明缺乏(某种)回归属性,而不是完全的正确性。

        很多基于属性的测试套件试图(可选地)通过支持一个确定的随机数发生器来获得确定性。这种方法很有用,但它留给用户的可能仍然是一个脆弱的测试套件,在套件中用户生成策略或测试用例的微小改变都会导致所考虑的测试用例集合的巨大变化。如果一个开发人员对其他测试中所见到的用例进行更改,向数据生成策略中添加一条分支以支持他们的新特性,那么他可能会得到海量的怪异错误。


我所需要的工作流

        以上述动机作为背景知识,我想要展示一下我认为我所需要的脱胎自基于属性测试的工作流。基于我的经验判断,该流程设计能够有效的弥补许多现有工具之间的隔阂,并满足大规模回归测试套件的需求。该描述的灵感来源于综合考虑处理上述关切问题,以及利用业已广泛应用于模糊测试领域,尤其是afl和oss-fuzz工具中的技术。

        硬编码用例

        我想要通过编写强制传递一些类型的所有输入的函数,以基于属性的样式来编写测试用例。然而,我同样想要以文本、人类可读格式(理想情况下,例如源代码文本或JSON格式)向我的代码储存库提交测试用例列表,以便回顾和整合。

        当以默认模式(例如,make test或者你的持续集成系统入口)运行时,测试工具应该仅仅运行这些用例。通过运行一个固定。小型的用例集合,我们获得上文所需求的速度和可靠性。

        用例生成

        到目前为止,我所描述的知识一个“表驱动测试”的轻量级版本,而不具备通常用于定义基于属性测试的生成性。由于我是表驱动测试的忠实拥趸,因此很明显自动化测试生成应该拥有一些特性,来提供基于并改善这类测试的功能。

        为了弥补隔阂,我希望在提交之前,在大多情况下,上文所提到的输入列表应该由基于属性的框架来生成。在编写一个测试之后,我可能会运行proptest generate命令,来开始一个传统的基于属性测试生成并最小化的过程;所不同的是,任何失败的测试用例,以及其他测试用例的样本,将自动写入“examples”文件,用于回顾以及开发人员可能的提交行为。

        覆盖范围指导的生成和反馈

        proptest generate命令如何确定保存哪个用例,或者何时拥有足够的测试用例?在此我们从AFL软件中获取灵感,使用覆盖范围指导的探测标准来指导测试用例生成过程,同时决定何时停止。生成器综合考虑以下因素,来决定是否停止生成测试用例:

        ·可配置的超时时间

        ·覆盖范围何时停止增长

        ·覆盖范围何时在绝对意义上足够高

        (完成之后,它还可以打印关于本次探测过程和覆盖范围的统计信息,来通知用户测试用例和/或生成器的质量。)

        一旦它停止(或者可能与生成过程同步停止),工具将通过一个覆盖范围驱动的最小化进程(类似afl-cmin命令的功能)来找到一个近似最小化的测试用例列表,从而能够遍历与完全语料库大概相同的覆盖范围集合。另外,任何失败的测试用例将自动保存(或者考虑独特性,或通过执行路径和相关标准进行判断)。

        上述进程的最终结果是一个小型测试用例语料库,该集合能够快速执行,同时又能将尽可能多的代码置于测试范围之内,并且显式检查一旦失败的测试用例,来避免已知错误的回归退化。

        探测模式

        最后,该工具也将拥有一个“无限模糊测试”模式,在该模式下它使用覆盖范围指导的探测引擎来持续不断地生成并检测新的测试用例,报告所发现的任何错误。作为一个开发人员,我可以定期在空闲周期内运行该模式,可以在笔记本电脑或服务器上的某处,或者理想情况下,在为该目的而设计的云端执行器上(比如oss-fuzz)。

        重要的是,由于该探测过程将作为一个独立工作运行,因此它将在与我的主要开发进程异步的状态下发现程序错误,而不会阻塞PR(性能需求)或破坏流程。我可以在我认为合适的任何时间线上分类并定位这些程序错误,并构造我自己的严重或紧急判断标准。

        另外,该模式下所发现的任何错误都将生成一个输出,其中包含了一个格式化的用例,我可以直接将其复制-粘贴到待提交的用例列表中并提交。这简化了本地重现过程,因为一条make test命令将运行该测试用例并向我展示测试失败过程,同时通过在修复之后每次持续集成系统运行时直接测试该测试用例,来确保该错误绝不会回归退化。


其他方面

        给定这样一个系统,工具同样可以实现“传统”属性测试模式,该模式忽略硬编码语料库,每次测试运行固定时间或固定数量的用例。我们也可以将一个给定测试用例标记为“永不自动生成的输入”,从而得到经典的表驱动测试。这种复杂性使得工作流能够以一种统一框架,适应从针对小型代码库的小规模测试,到非常大型复杂的测试流程。

        类似的,甚至对于已经提交用例的测试来说,如果更改了待测试的代码,那么你应该能够以现有用例为生成种子重新运行生成器,针对新代码重新启动探测流程,并且生成新的最小化的高覆盖率语料库。很明显这个流程并不是必需的——如果属性没有改变,那么旧用例应该仍然可用——但重要的是,我们可以通过一个演化的实现方法来使我们的语料库和测试数据得到进化。


总结

        我很确信的一点是,如果能够实现以上所描述的(抽象)系统行为,我将非常乐意在我自己的代码中采用属性测试框架。

        对于上述工作流的很多方面我有十足的信心,因为很大程度上来说我只是描述了存在于模糊测试领域的工具,因此这并不是完全凭空捏造的。即便如此,我认为对于这样的工作流如何作为测试代码的基本方法来运作,仍然存在一些公开的问题;如果你关注过或者尝试过类似的工作,我非常有兴趣倾听相关的经验报告。

        最后,我想再一次帮Hypothesis软件打响知名度;尽管它并不是完全按照我在上文中所描述的工作流来实现的,但它基本包含了构建该工作流所需的全部内容,包括失败用例的持续用例数据库,人工指定用例的能力以及覆盖范围指导的探测过程。

        这些日子以来我没有写太多的Python代码,但如果我有的话,我一定会在Hypothesis软件的基础上尝试搭建一个这种工作流的变体版本。




原文地址:https://blog.nelhage.com/post/property-testing-like-afl/

本文由  看雪翻译小组  木无聊偶  编译

转载请注明来源


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回