先做判定器,再开始缩小
一句话总结
构建最小复现用例,有一半的工作不是精简技巧,而是让机器来判定“是否仍然是同一个失败”。没有判定器,精简就成了猜测。
为什么需要它
客户发来的复现步骤,通常就是那个人当天做过的全部事情:附带一份 12 步的步骤和一份 200 行的输入文件。带着这一切去找原因,需要查看的组合会爆炸,你会从随便哪个地方开始,两天之后仍然停在原地。
大家都知道要精简,问题是精简着精简着就迷了路。删掉一行,程序因为另一个错误崩溃了。另一个错误也是错误,于是误以为“仍然能复现”,继续精简。这样得到的三行输入,复现的是与我们要找的缺陷毫无关系的另一个缺陷。拿着这三行去找客户的开发团队,一天就白白浪费了。
为什么判定器要先做
所以有一个顺序:精简之前先做判定器。判定器(oracle)是这样一个程序:接收一个候选输入,只回答“它现在是否产生了我们在追查的那个失败”。靠人眼来判断,无法用于精简。精简要把候选试上几十到几百次,而这个次数人是承受不了的。
判定器要看的大致有三样。
- 退出码:只知道不是 0 还不够,还要看具体是哪个退出码。
- 特征字符串:只在该失败中才会出现的错误名称或异常名称,越精确越好,例如
ERR-QTY-COMMA。 - 位置:必要时还要看是在哪个步骤发生的。有的程序会在两个位置抛出同一个异常。
反过来,也有判定器不该看的东西。运行时间、临时文件名、日志行数这类每次运行都会变的内容,不要放进判定。放进去的话,同一个候选有的日子能复现,有的日子不能。
工作原理
有了判定器,精简就由机器来做。最简单的办法是一行一行地删。200 行就调用 200 次判定器,每走完一遍剩下的行数减少,于是又要再走一圈。结果是对的,但很慢。
Zeller 和 Hildebrandt 的 ddmin则以块为单位来做。把候选分成 n 片,先看只用其中一片能不能复现,不行的话再看去掉一片后的其余部分能不能复现。两种都不行,就切得更细,重复同样的事。如果起作用的只有两行,调用次数会降到接近行数的对数:在 256 行中找出两行,只需要调用一百次出头的判定器。
[a b c d e f g h] 전체가 재현된다
갈라 본다 → [a b c d] [e f g h]
[a b c d] 재현 안 됨
[e f g h] 재현 안 됨 ← 조각 하나만으로는 안 된다
나머지로 본다 → [e f g h](= a b c d 를 뺀 것) 안 됨, [a b c d] 안 됨
더 잘게 → [a b] [c d] [e f] [g h]
[a b] 뺀 나머지 재현됨 → 후보가 [c d e f g h] 로 줄었다
… 반복 …
결과: [c g] — 여기서 c 나 g 를 빼면 재현되지 않는다
该代码块中的韩文依次说明:整体可以复现;分成两半来看,只用其中一半都无法复现,也就是单独一片不行;再看去掉一半后的其余部分,也都不行;切得更细,去掉 [a b] 后的其余部分可以复现,候选缩小为 [c d e f g h];重复这个过程;最终结果是 [c g],从中去掉 c 或 g 都无法复现。
结果是 1-最小(1-minimal),意思是剩下的内容中去掉任何一个,复现就会停止。这并不意味着“最小”:也许还存在完全不同的更小的组合。所以精简结束之后,要把剩下的每一行逐个去掉并确认复现停止,并且单独写下这份证明。没有这份证明,就回答不了“为什么需要这一行”这个问题。
在现场相遇的样子
第一,步骤也要像输入一样精简。12 步中实际需要的只有四步,这种情况很常见。其余八步是负责人的习惯。精简了步骤,“怪环境”这种含糊的解释就消失了。
第二,在干净的地方运行。如果前一次运行留下了文件,即使去掉“创建配置文件的步骤”也能复现,于是会得出“这一步不需要”的错误结论。每次试验步骤时,都要新建一个空目录。
第三,改变取值来确认精简出的用例是不是真的。如果剩下的两行是 locale=de 和 qty=1,5,就看把数字改成 9,25 之后是否仍能复现。能复现的话,原因就不在那个数字上,而在于逗号和区域设置(locale)的组合。做到这一步,才成为客户开发团队可以立刻修复的报告。
第四,最小用例要连同判定器一起发送。只发两行输入,接收方会先问“这为什么是个问题”。连同判定器一起发送,接收方在自己那里运行一次,就会立刻站到同一个位置上。
实际工作中真正重要的事
- 先做判定器。颠倒这个顺序,精简就成了猜测。
- 任何失败都不等于同一个失败。要同时看退出码和特征。
- 写下 1-最小的证明。把逐行去掉并确认复现停止的过程留成记录。
- 确认是结构而不是取值。改变取值后仍能复现,说明原因是结构。
下一项实验要做什么
展开(虚构的)Daerim 物产发来的故障包:一个处理程序、200 行的 feed,以及 12 步的复现步骤。先做出判定“同一个失败”的判定器,用它来精简 feed,再把剩下的每一行逐个去掉,证明结果是 1-最小。接着亲自实现 ddmin,把精简自动化:评分器会在每次把答案藏在不同位置的 256 行输入上运行你的缩减程序,同时查看结果是否正确,以及调用了多少次判定器。最后精简 12 步的复现步骤,确认改变取值后是否仍能复现,并写成报告提交。