测试用例减小器是被低估的调试工具
测试用例减小器不如应有的那样知名,而了解它们的人并不总是意识到我们可以使用—甚至滥用!—它们的多种方式。在这篇文章中,我将探讨一些在使用这些奇妙工具时学到的东西。我会从基础开始,因为这个想法太简单,以至于令人难以置信它能有效。我将逐步深入,带来更深层次的惊喜。测试用例减小器尝试减少输入的长度,但我们可以强迫它们考虑额外因素,例如错误发生的频率或执行的指令数量。根据您要调试的问题,这可能会对测试用例减小器的实际效果产生巨大的影响。我不期望这篇文章中的任何内容会让专家感到惊讶,但由于我是通过艰难的方式了解这些内容的,也许其他人能从中受益,将一些内容集中在一个地方。测试用例缩减 假设我们编写了一个在大输入上崩溃的程序,而我们不知道输入的哪部分导致崩溃:我们可以做什么?我们中的大多数人可能会首先使用经典技术调试我们的程序,逐步从快速脏乱(printf)转向更有原则的(调试器),如果我们绝望而经验丰富,甚至使用“异国情调”的工具(清理工具、valgrind等)。每个程序员最终都会拥有一套调试工具和技术,他们会用其中一些,有些比其他的更有效。一个使用频率低于应该使用频率的技巧是减少输入的大小。在几乎所有情况下,输入越小,我们就越容易找出它导致的问题。这种缩减可以是手动的。我们可以将输入加载到文本编辑器中,删除一部分,然后查看新的较小输入是否仍然导致崩溃。毫不奇怪,作为容易厌倦的有限视野的人类,我们在手动执行时往往会错过许多缩减机会。通常情况下,删除部分输入会阻止我们正在调查的程序崩溃:减少后的程序可能会完全运行,或在新的输入上抛出不同的、正确和预期的错误。更糟糕的是,在某个时候人们意识到,删除输入的A部分并没有达到我们想要的效果,但删除不相交的A和B部分却达到了:删除的搜索空间有多大?!一条西西弗斯的未来在召唤。测试用例减小器 幸运的是,有工具可以自动化测试用例减少的过程:测试用例减小器。这些工具会接受一个程序,一个输入,以及一个有趣性测试。测试用例减小器会尝试输入的越来越短的版本,而有趣性测试会告诉减小器这些短版本是否仍然触发您关心的问题。测试用例减小器可以惊人地有效——95-99%的减少是常见的——并且通常使调试变得更简单。测试用例减小器听起来像是魔法:一个工具如何知道要删除输入的哪部分?更糟的是,最彻底接受它们的社区是编译器作者,许多程序员认为他们是一群技艺高超的精英。对我而言,这两者的结合让许多人对尝试这样的工具却步。好消息是,测试用例减小器并不是魔法。看到这一点的最简单方法是编写一个。让我们假设我写了这个从文件中读取单词的程序:import sys for l in open (sys.argv[1]): if len(l) > 25: print("单词太长\n") 当我在我的机器上用/usr/share/dict/words运行这个时,它会打印警告:$ python3 t.py /usr/share/dict/words 单词太长 为了这个例子,我们假设看到这个警告是一个“错误”,而我们还不能用传统的调试技术弄清楚为什么它崩溃。测试用例减小器能帮我们吗?我们需要做的第一件事情是定义我们的有趣性测试。我将有意遵循我熟悉的测试用例减小器的约定,说明一个有趣性测试是一个程序,它: 如果输入是“有趣的”——即输入表现出我们感兴趣的错误——则返回0,并且我们应该继续使用此减少的输入。 如果输入是“无趣的”,则返回非0,我们需要尝试不同的缩减。 我将编写一个简单的shell脚本,将文件名作为第一个参数,运行我上面的程序,并检查它是否产生“单词太长”的输出。如果是,我的有趣性测试将返回0;如果不是,它将返回1。 #! /bin/sh if python3 t.py "$1" | grep "单词太长" > /dev/null; then exit 0 else exit 1 fi 现在我们需要做的部分是:减小器!幸运的是它并不太困难: #! /usr/bin/env python3 import subprocess, sys, tempfile cur = [x.rstrip() for x in list(open(sys.argv[2]))] i = 0 while i < len(cur): with tempfile.NamedTemporaryFile (mode='w') as p: cnd = cur[:] del cnd[i] p.
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡