白盒测试工具_使用复杂的用户定义功能对大数据分析进行白盒测试

bbd07f2d1cb4c75ea3cd49cb700cd7e3.png

引用:Gulzar M A, Mardani S, Musuvathi M, et al. White-box testing of big data analytics with complex user-defined functions[C]. foundations of software engineering, 2019: 290-301.

摘要:Google 的 MapReduce,ApacheHadoop 和 ApacheSpark 等数据密集型可扩展计算(DISC)系统可用于处理云中的大量数据。现代 DISC 应用程序对自动测试提出了新的挑战,因为它们由数据流运算符组成,并且复杂的用户定义函数(UDF)不同于 SQL 查询。我们设计了一种新的白盒测试方法,称为“BIGTEST”,以推断 UDFs 和每种数据流、关系运算符所创建的等价类的内部语义的一致性。

我们的评估结果表明,尽管输入数据的规模非常大,但对于实际的 DISC 应用的测试覆盖率往往是有很大偏差、不够充分的,有 34%的 Joint Dataflow and UDF (JDU)路径依然未被测试到。BigTest 显示出了为本地测试进行 10^5 至 10^8 的数量级的数据精简的潜力,而且所能发现的人工注入缺陷的数量是旧方法的两倍多。我们的实验表明,实际上仅需要很少的数据记录(数十条的数量级)即可达到相同的 JDU 覆盖率。测试数据的减少还可以平均节省 194 倍的 CPU 时间,这表明交互式和快速的本地测试对于大数据分析是可行的,而无需在巨大的生产数据上测试应用程序。

1. 引言

如今,诸如 Mapreduce,ApacheHadoop,ApacheSpark 等数据密集型可扩展计算(DISC)系统通常用于处理 TB 和 PB 级别的数据。在这种规模下,稀有和奇怪的情况经常在生产中出现。因此,对于这些应用程序来说,运行数天后发生崩溃甚至更严重的情况很常见。不幸的是,用于测试这些应用程序的通用行业实践仍然在使用本地随机采样的输入并运行,显然,它们消除不了那些隐蔽的 bug。本文介绍了一种称为 BigTest 的系统输入生成工具,它使用了针对 DISC 应用程序的一种新的白盒测试技术。BigTest 受到了最近出现的系统性测试生成工具的启发。与现有测试工具所针对的通用程序不同,DISC 应用程序使用关系运算符(例如 join 和 group-by)和数据流运算符(例如 map,flatmap)以及用户定义的函数(UDF)编写的通用语言(如 C / C ++,Java 或 Scala)进行组合。

为了全面测试 DISC 应用程序,BigTest 解释了 UDF 与关系和数据流操作的结合行为。一种简单的方法是将这些操作替换为其实现,并象征性地执行生成的程序。组合路径约束的集合被转换为 SMT(可满足性模理论)查询,并通过利用现成的定理证明者 Z3 或 CVC4 进行求解,以产生一组具体的输入记录。通过使用这种组合方法,BigTest 比以前的 DISC 测试技术更有效,后者既不考虑 UDF 也不将其视为未解释的功能。

为了实现这种方法,BigTest 解决了了三个重要问题,我们的评估表明这些挑战对工具的有效性至关重要。第一,除了每个数据流运算符的常规非终止案例外,BigTest 还对终止案例进行建模。例如,两个表的联接的输出仅包括键与两个输入表都匹配的行。第二,BigTest 显式地对集合进行建模,这些集合由 flatmap 创建并由 reduce 使用。第三,BigTest 分析字符串约束,因为字符串操作在 DISC 应用程序中很常见,并且在分段和解析过程中常见的错误是 ArrayIndexOutOfBoundException 和 StringIndexOutOfBoundsException。

为了评估 BigTest,我们使用了一个基准测试,该基准测试是从先前的工作中选择的 7 个真实世界的 Apache Spark 应用程序。虽然这些程序代表 DISC 应用程序,但它们不足以代表此域中发生的缺陷。为了纠正此问题,我们对 Stack Overflow 和邮件列表中报告的 DISC 应用程序缺陷进行了调查,并确定了 7 类缺陷。

我们评估了 JDU(联合数据流和 UDF)的路径覆盖范围,符号执行性能和 SMT 查询时间。我们的评估表明,在 DISC 应用程序的测试范围方面,现实世界的数据集通常存在明显的偏差和不足,仍然有 34%的 JDU 路径未经测试。与 Sedge 相比,BigTest 大大增强了其对 DISC 应用程序建模的能力。

我们证明了 JDU 路径覆盖与缺陷检测情况的改善直接相关——BigTest 发现的手动注入缺陷平均比 Sedge 多 2 倍。与对整个生产数据进行测试的代码相比,BigTest 可以将本地测试的数据大小最小化 10^5 到 10^8 的数量级,平均节省 CPU 时间 194 倍。BigTest 会平均在 19 秒内为所有剩余的未测试路径合成具体的输入记录。下面,我们重点介绍贡献摘要。

a)BigTest 是 DISC 白盒测试的第一部分,它全面地对数据流运算符和用户定义函数的内部路径进行建模。

b)BigTest 进行了三项重要的改进,以提高 DISC 应用程序的缺陷检测能力:(1)考虑了每个数据流运算符的终止和非终止情况;(2)显式地建模由 flatmap 创建的集合,并将聚合逻辑转换为迭代聚合器;(3)显式地对字符串约束建模。

c)提出了手动注入的 DISC 应用程序缺陷以及生成的测试数据的基准,其灵感来自 StackOverflow 和邮件列表所显示的现实世界中 DISC 应用程序缺陷的特征。

d) BigTest 发现的缺陷比 Sedge 多 2 倍,最大程度地减少了测试数据的数量,并且快速,可交互。

我们的结果表明,大数据分析的交互式本地测试是可行的,并且开发人员无需在整个生产数据上测试其程序。例如,用户可以监视从 BigTest 生成的等效路径类别的路径覆盖范围,并跳过属于已覆盖路径的记录,从而构建用于本地开发和测试的生产数据的最小样本。

2. 方法

BigTest 利用定理验证器 Z3 和 CVC4,将 Scala 中的 Apache Spark 应用程序作为输入,并生成测试输入以覆盖程序的所有路径。

2.1 数据流程序分解

DISC 应用程序由有向无环图组成,其中每个节点代表一个数据流运算符,例如 reduce 和对应的 UDF。由于 Apache Spark 中数据流运算符的实现涉及数十万行代码,因此与 Spark 框架代码一起执行 DISC 应用程序的符号执行是不可行的。事实上,我们根据逻辑规范抽象出数据流运算符的内部实现。我们将 DISC 应用程序分解为数据流图,其中每个节点调用一个 UDF,并通过数据流运算符的逻辑规范与 UDF 的符号执行相结合起来。

UDF 抽取。BigTest 将 DISC 应用程序编译为 Java 字节码,并遍历每个抽象语法树(AST),以搜索与每个数据流运算符相对应的方法调用。这种方法调用的输入参数是表示为匿名函数的 UDF,如图 4b 所示。BigTest 将 UDF 作为单独的 Java 类存储(如图 4c 所示)并生成 JPF 所需的用于符号执行的配置文件。BigTest 还执行依赖关系分析,以包括 UDF 中引用的外部类和方法。

32cb1d617090bb260d0d5355857d3a06.png

f9744aa75b425c8251da5e0e5e97c8d5.png

处理聚合器逻辑。对于聚合运算符,附加的 UDF 必须转换形式。例如,用于 reduce 的 UDF 是一个的二元函数,它对图 5a 所示的集合执行增量聚合。我们将其转换为带有图 5b 所示循环的迭代版本。

2.2 数据流运算符的逻辑规范

本节描述了由每个数据流运算符的语义生成的等效类。对于特定的运算符,我们使用 C1 表示输入数据 I 上的一组路径约束。C1 中的单个元素 c 包含路径约束,执行相应的唯一路径必须满足该约束。我们将 f 定义为 UDF 的一组符号路径约束的集合,其中 f(t)表示输入 t 所执行的唯一路径的约束。通过将数据流运算符的实现抽象为逻辑规范,BigTest 不必象征性地执行 Spark 框架代码,因为它只关注应用程序级别的错误,而不是框架实现的错误,这不在本文讨论范围之内。BigTest 支持所有流行的数据流运算符,但已弃用的运算符除外。

42c89bacd7115893a6814cd71267ee1a.png

82ae25669b7f2ecfa8cea4019ed831bc.png

58050d45662da1f8314a068d8119b2d1.png

2.3 路径约束的生成

本节介绍符号路径查找器(SPF)中的一些增强功能。DISC 应用程序广泛使用字符串操作操作,并依赖 Tuple 数据结构来启用基于键值的操作。在 UDF 上简单地使用现成的 SPF 不会产生有意义的路径条件,因此,在测试过程中会忽略存在的缺陷。

c2bb907012f69c51f8966d1f504f105a.png

字符串。诸如 split 的用于将输入记录转换为键值对的操作在 DISC 应用程序中很常见,但 SPF 不支持。BigTest 通过捕获对 split 的调用,记录定界符并返回字符串数组来扩展 SPF 的功能。当请求此数组的第 n 个元素时,SPF 返回一个符号字符串,该符号字符串编码为带有相应索引的 splitn。

04f615a1e336c4ce3f2f54ef9bcac9a5.png

集合。在 DISC 应用程序中,通过诸如 flatmap 之类的运算符构造和处理集合是必不可少的过程。因此,BigTest 显式地对在集合上应用 UDF 的效果进行建模。在图 7 中,BigTest 产生的聚合器逻辑的迭代版本将一个集合作为输入,并对每个元素求和(如果元素大于或等于零)。假设用户提供的边界 K=3,BigTest 将循环展开三遍,并生成四对路径条件(P)和相应的结果(E):

50c73c36698225950aec7ec9767874a1.png

5d1851f608d4b56b83061cbe2867039c.png

ae4ff67998e9c8b68b672306fb56d9a0.png

45f1a607943e321e8993b3bc945f9d41.png

3cb27c581de4628f53c0140484a49974.png

af4f867a5316a88306da8a68ad81a2d2.png

2.4 测试数据生成

02a7c38567c13a6e3c96ec8ca9d244d3.png

BigTest 会为 Z3 不支持的 Java 本机方法生成解释函数。例如,BigTest 用类似的 Z3 函数替换 isInteger。BigTest 会分别执行每个 SMT 查询。尽管独立执行每个 SMT 查询可能会导致重叠约束的冗余解决,但在我们的实验中,我们并未将其视为性能瓶颈。从理论上讲,由于分支和循环,路径约束的数量呈指数增长。但是,根据经验,我们的方法非常适合 DISC 应用程序,因为 UDF 往往比 DISC 框架小得多(按几百行),并且我们使用逻辑规范对框架实现进行抽象。

图 8 显示了 BigTest 为图 2 生成的 SMT 查询。第 1 至 6 行将第一个表压缩为四个段,将第二个表压缩为两个段,并以逗号分隔。第 7 至 10 行将字符串限制为有效的整数。为了强制执行跨越字符串和整数边界的约束,BigTest 使用自定义函数 isInteger 和 Z3 函数 str.to.int。第 11 至 14 行强制执行一个包含“ Palms”且速度小于或等于 15 的记录。第 15 至 19 行将 UDF 生成的这些约束与随后的数据流运算符结合在一起。

73b0a84df5f9e19713c6268859c576bb.png

3. 评估

我们使用各种基准 DISC 应用程序评估 BigTest 的有效性和效率。我们将 BigTest 与 Sedg 在路径覆盖范围,缺陷检测能力和测试时间方面进行了比较。我们比较了三种替代测试方法的测试充分性,输入数据大小和潜在的节省时间:(1)对 k%记录的随机采样,和(2)使用前 k%记录的子集,以及(3)在完整的原始数据上测试。

  • BigTest 在何种程度上适用于 DISC 应用程序?

  • BigTest 可以实现多少测试覆盖率的提高?

  • BigTest 可以检测多少个缺陷?

  • BigTest 可减少多少测试数据?

  • BigTest 需要多长时间来生成测试数据?

3.1 数据流程序支持

BigTest 支持 DISC 应用程序中流行的各种数据流运算符。例如,Apache Spark 提供 flatmap 和 reduceByKey 来构造和处理集合。Sedge 的先前方法是为 PIG Latin 设计的,只有少数运营商支持。Sedge 既不是开源的,也没有任何可用于 Apache Spark 的工具以进行直接比较。我们通过删除 UDF 的符号执行和某些运算符的等效类来模拟 Sedge 来手动降级 BigTest。Sedge 和 BigTest 的实现都是公开可用的。在用 Apache Spark 编写的七个基准应用程序中,五个应用程序包含 flatmap 和 reduceByKey,因此,Sedge 无法为这五个应用程序生成测试数据。

3.2 连接数据流和 UDF 路径覆盖

JDU 路径覆盖评估。我们将 BigTest 与三种替代采样技术进行了比较:(1)对原始数据集的 k%进行随机采样;(2)由于开发人员经常使用 head-n 测试 DISC 应用程序,因此选择了原始数据集的前 k%;以及(3)先前的做法。为了保持实验设置的一致性,我们枚举了给定用户提供的边界 K 的 JDU 路径,并测量了每种方法覆盖了多少条路径。

图 9 比较了 BigTest,Sedge 和原始数据集的测试覆盖率。Y 轴表示归一化的 JDU 路径覆盖率,范围从 0%到 100%。在七个主题计划中,我们观察到 Sedge 覆盖的 JDU 路径明显减少(BigTest 覆盖的范围为 22%)。通过不对 UDF 的内部路径建模,Sedge 无法探索许多 JDU 路径。即使使用完整的数据集,JDU 路径覆盖率也仅达到 BigTest 可以实现的 66%。整个数据集比 Sedge 具有更好的覆盖范围,但是与 BigTest 相比,它仍然覆盖范围仍不够大。换句话说,使用整个大数据进行测试并不一定会达到很高的测试充分性。

在图 10 中,随机 1%样本和前 1%样本中都有 59%是被 BigTest 覆盖的。我们执行另一个实验,以测量不同样本量对 JDU 路径覆盖率和测试执行时间的影响。图 11a 和图 11b 显示了 CommuteType 上的结果。在 CommuteType 中,当所选数据的百分比从 0.1%增加到 50%时,覆盖的 JDU 路径从 2 条增加到 6 条。对于那些小样本,输入表没有匹配键以执行下游运算符,并且 time 和 distance 列可能没有特定值以执行 UDF 的所有内部路径。就运行时间而言,随着样本大小(k)的增加,测试执行时间也呈线性增加(请参见图 11b,其中 x 轴为对数刻度)。

90e95e95a072dbd0fb8636a1dd091575.png

3.3 缺陷检测能力

我们通过手动注入常见缺陷来评估 BigTest 检测缺陷的能力。出于数据隐私的原因,DISC 应用程序很少开放源代码,也没有带缺陷的 DISC 应用程序的现有标准,因此我们通过研究实际 DISC 应用程序 bug 的特征,并基于此研究来注入缺陷来创建一组带缺陷的 DISC 应用程序。

我们会仔细研究带有以下关键字的 Stack Overflow 和 Apache Spark 邮件列表并检查前 50 个帖子:Apache Spark exceptions, task errors, failures, wrong outputs。许多错误与性能和配置错误有关。因此,我们将这些内容过滤掉并分析与编码错误有关的 23 个帖子。对于每篇文章,我们都会通过阅读问题,发布的代码,错误日志,答案和可接受的解决方案来调查缺陷的类型。我们将调查结果归纳为七种常见缺陷类型:

(1)错误的字符串偏移量(2)列选择不正确(3)使用错误的定界符(4)错误的分支条件

(5)错误的联接类型(6)用一个值交换一个键(7)其他常见变异

2a49ff94578eba63bd8ebda022b9c995.png

如果适用,我们在每种应用中注入每种缺陷类型之一。例如,仅当使用 substr 或 split 方法时,才能插入缺陷类型 1 和 3。当缺陷类型适用于多个位置时,我们在相应的 StackOverflow 或 Mailing List 帖子中选择一个与之相似的位置。

Sedge 将内部 UDF 表示为未解释的函数,因此无法对所有内部 UDF 路径建模。相反,BigTest 通过符号表示 UDF 来将其视为解释函数,并对所有内部 UDF 路径(直到边界 k)进行建模,这对于对 UDF 内部进行高覆盖率测试至关重要。表 4 比较了 BigTest 和 Sedge 进行的故障检测。BigTest 检测到的注入故障数量比 Sedge 多 2 倍。

b13b9c8616a3e2de05b23a04e73a37bd.png

作为另一个示例,应用程序 P6 识别出 5 个以上挂科学生的课程。P6 的缺陷版本将过滤谓词 count> 5 替换为 count> 0,以输出至少有一名挂科学生的课程。P6 的原始版本使用 map 和 filter 解析每行并识别挂科的学生,reduceByKey 计算失败学生的数量,并使用 filter 查找超过 5 个挂科学生的课程。BigTest 会生成至少两个记录,以同时执行最后一个过滤器的终止和未终止案例;因此,原始版本和缺陷版本会产生不同的结果。另一方面,生成的记录仅适用于非终止案例。对于原始版本和缺陷版本,此类数据将产生相同的结果,无法检测到注入的缺陷,如表 5 所示。

3.4 测试数据精简

在整个数据集上测试 DISC 应用程序既昂贵又费时。BigTest 在保持相同测试覆盖率的同时最大程度地减小了数据集的大小。它仅生成少量数据记录来实现与整个生产数据相同的 JDU 路径覆盖率。七个基准中有四个基准具有附带的数据集,而其余基准则依赖于每个约 20GB 的综合数据集。比较结果如图 12 所示。在应用程序 P6 中,BigTest 生成 30 行数据所达到的 JDU 路径覆盖率比使用整个 4000 万条记录的数据集还多 33%。在所有基准测试应用程序中,BigTest 生成的数据范围为 5 到 30 行。这表现出了进行本地测试时显著减小数据集大小的潜力。

d4dff1bd17fd958ca26cfc92db1d916f.png

3.5 节省时间和资源

通过最大程度地减少测试数据而不影响 JDU 路径覆盖范围,BigTest 从而减少了测试运行时间。较小的测试数据有两个好处:(1)运行测试用例所需的时间更少,(2)运行测试的资源(工作节点,内存,磁盘空间等)也更少。

我们通过 BigTest 测量在一台机器上的总运行时间,并将其与包含整个输入数据集的 16 节点群集上的运行时间进行比较。我们列出了测试数据生成与在生成的数据上执行应用程序所需的总运行时间。图 13 展示了评估结果。

在应用程序 P6 中,在一台计算机上使用 BigTest 的数据进行测试需要 5.3 秒,否则在整个数据集上进行测试需要 387.2 CPU 秒,而该数据仍然缺乏完整的 JDU 路径覆盖。与使用整个数据集进行测试相比,在七个主题程序中,BigTest 将测试时间平均缩短了 194 倍。

图 14 展示了 BigTest 的总运行时间的分解。对于 Airport Layover(P3),观察到的最大测试生成时间为 70 秒,其中约束求解消耗了 66 秒。这是因为生成的 JDU 路径将整数算术和复杂的字符串约束一起包括在内。即使经过 BigTest 的优化,解决跨越不同维度边界的此类约束(整数运算与字符串约束)也很耗时。如果我们将测试运行时间和测试生成时间都结合起来,然后将 BigTest 与整个数据集的测试时间进行比较,则 BigTest 的表现仍会更佳。实际上,BigTest 仍然比对整个数据集进行测试快 59 倍。

74a8c18df20c254a2255f4839887b073.png

3.6 有界深度探索

BigTest 使用用户提供的边界 K 来限定循环展开的次数。我们评估了将 K 从 1 更改为 5 的影响,并将结果展示在图 15 中。在 K=2 时,用于 GradeAnalysis 的 JDU 路径数为 36。当 K 为 3 时,BigTest 会生成 438 条 JDU 路径。随着 K 增加,可以在整个主题程序中看到测试生成时间呈指数级增长。当 GradeAnalysis 中的 K=2 时,BigTest 花费 12 秒,而当 K=3 时,BigTest 花费 204 秒。我们凭经验发现 K=2 是循环迭代的合理上限,以避免路径数量爆炸式增长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值