代码之家  ›  专栏  ›  技术社区  ›  Edward Z. Yang

不变测试能代替单元测试吗?

  •  32
  • Edward Z. Yang  · 技术社区  · 16 年前

    作为一名程序员,我全心全意地购买了TDD的理念,并努力为我编写的任何非琐碎的代码进行广泛的单元测试。有时这条路会很痛苦(行为变化导致级联多个单元测试变化;需要大量的脚手架),但总的来说,我拒绝在没有测试的情况下编程,而这些测试可以在每次更改之后运行,因此,我的代码就不那么麻烦了。

    最近,我一直在玩哈斯克尔,它是常驻测试库,快速检查。在与TDD截然不同的方式中,QuickCheck强调测试代码的不变量,也就是说,某些属性持有所有(或实质性子集)的输入。一个简单的例子:一个稳定的排序算法应该给出相同的答案,如果我们运行它两次,应该有增加的输出,应该是输入的排列等。然后,快速检查生成各种随机数据,以测试这些不变量。

    在我看来,至少对于纯函数(也就是说,没有副作用的函数——如果你正确模拟了,你可以将脏函数转换为纯函数),不变测试可以取代单元测试作为这些功能的严格超集。每个单元测试由一个输入和一个输出组成(在命令式编程语言中,“输出”不仅是函数的返回,而且是任何更改的状态,但它可以被封装)。可以想象,您可以创建一个随机输入生成器,它足以覆盖您手动创建的所有单元测试输入(然后是一些,因为它将生成您不可能想到的情况);如果您在程序中发现了由于某些边界条件而导致的错误,则可以改进您的随机输入生成器。o它也会产生这种情况。

    那么,挑战在于是否有可能为每个问题建立有用的不变量。我想说的是:一旦你有了一个答案,看看它是否正确,比一开始计算答案要简单得多。对不变量的思考也有助于澄清复杂算法的规范,这比特殊的测试用例要好得多,后者鼓励对问题进行逐案思考。您可以使用程序的早期版本作为模型实现,或者使用其他语言的程序版本。最后,您可以覆盖以前的所有测试用例,而不必显式地对输入或输出进行编码。

    我是疯了,还是有什么事?

    4 回复  |  直到 9 年前
        1
  •  21
  •   Edward Z. Yang    14 年前

    一年后,我现在想我对这个问题有了答案: 不! 特别是,单元测试对于回归测试总是必要和有用的,在回归测试中,一个测试被附加到一个bug报告中,并保存在代码库中,以防止该bug再回来。

    但是,我怀疑任何单元测试都可以替换为一个随机生成输入的测试。即使在命令式代码的情况下,__input_157;也是您需要执行的命令式语句的顺序。当然,创建随机数据生成器是否值得,以及是否可以使随机数据生成器具有正确的分布是另一个问题。单元测试只是一种退化情况,在这种情况下,随机生成器总是给出相同的结果。

        2
  •  9
  •   Anthony    16 年前

    你提出的是一个非常好的观点——当只适用于函数式编程时。你说了一种用命令式代码来完成这一切的方法,但是你也提到了为什么不这样做——这并不特别容易。

    我认为这就是它不能取代单元测试的原因:它不太适合命令式代码。

        3
  •  0
  •   user1250537    13 年前

    您在原始文章中所写的内容提醒了我这个问题,这是一个关于循环不变量是什么来证明循环正确的开放性问题…

    不管怎样,我不确定你在正式的规范中读了多少,但是你正在沿着这条思路前进。大卫·格里斯的这本书是这方面的经典之作,我仍然没有很好地掌握这个概念,无法在日常编程中快速使用它。对正式规范的通常反应是,它是困难和复杂的,并且只有当您在安全关键系统上工作时才值得努力。但我认为有一些类似于QuickCheck公开的封底技术可以使用。

        4
  •  0
  •   Nathan Long    9 年前

    可疑的

    我只听说过(没有用过)这种测试,但我看到了两个潜在的问题。我很想对每一个都发表评论。

    误导性结果

    我听说过这样的测试:

    • reverse(reverse(list)) 应平等 list
    • unzip(zip(data)) 应平等 data

    很高兴知道这些对于广泛的输入都是正确的。 但如果函数只返回其输入,这两个测试都将通过。

    在我看来,你想核实一下,例如, reverse([1 2 3]) 等于 [3 2 1] 在至少一个案例中证明正确的行为,然后 添加 一些随机数据测试。

    测试复杂性

    完全描述输入和输出之间关系的不变测试可能比函数本身更复杂。如果它很复杂,可能有问题,但您没有测试。

    相比之下,一个好的单元测试太简单了,不可能把读者搞砸或误解。只有输入错误才能在“expect”中创建错误 反转([1 2 3]) 平等 〔3 2 1〕 “。