作为程序员,我全心全意地购买了TDD理念,并努力为我编写的任何非平凡代码进行广泛的单位测试。有时这条路可能会很痛苦(行为改变会导致多个单位测试变化级联;需要大量的脚手架),但是总的来说结果。

最近,我一直在与Haskell一起玩,它是居民测试库,QuickCheck。以与TDD明显不同的方式,QuickCheck强调测试代码的不变式,即某些属性,这些属性在所有(或实质性的)输入中都具有。一个快速的示例:稳定的排序算法应给出相同的答案,如果我们运行两次,应该增加输出,应该是输入的排列等。那么,QuickCheck会生成各种随机数据以测试这些不变性。

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

因此,挑战是是否有可能为每个问题制定有用的不变性。我要说的是:一旦有了答案,就可以简单得多,看看它是否正确,而不是首先计算答案。对不变性的思考也有助于阐明复杂算法的规格比临时测试案例好得多,这鼓励对问题进行逐案的思考。您可以将程序的先前版本用作模型实现,或其他语言的程序版本。最终,您可以覆盖所有以前的测试案例,而无需明确编码输入或输出。

我发疯了,还是我正在做某事?

有帮助吗?

解决方案

一年后,我现在认为我对这个问题有答案: 不! 特别是,单位测试将始终是必要和有用的,对于回归测试,其中将测试附加到错误报告中,并在代码库中使用,以防止该错误返回。

但是,我怀疑可以用随机生成的输入的测试代替任何单位测试。即使在命令式代码的情况下,“输入”是您需要制作的命令式陈述的顺序。当然,无论是否值得创建随机数据生成器,以及您是否可以使随机数据生成器具有正确的分发是另一个问题。单位测试只是一个退化的情况,随机发生器总是给出相同的结果。

其他提示

您提出的是一个很好的观点 - 仅应用于功能编程时。您说明了一种用命令式代码来完成这一切的方法,但是您还谈到了为什么不这样做 - 这并不是特别容易。

我认为这就是它不会替代单元测试的原因:它不适合适合命令代码。

我只听说过(未使用)这类测试,但我看到了两个潜在的问题。我很想对每个人发表评论。

误导结果

我听说过这样的测试:

  • reverse(reverse(list)) 应该等于 list
  • unzip(zip(data)) 应该等于 data

很高兴知道这些投入范围很广。 但是,如果功能仅返回输入,这两种测试都将通过。

在我看来,您想验证这一点,例如 reverse([1 2 3]) 等于 [3 2 1] 为了在至少一种情况下证明正确的行为,然后 添加 一些随机数据测试。

测试复杂性

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

相比之下,良好的单位测试太简单了,无法搞砸或误解读者。只有错字可以在“期望 reverse([1 2 3]) 等于 [3 2 1]".

您在原始帖子中写的内容使我想起了这个问题,这是一个空旷的问题,即循环不变是为了证明循环正确...

无论如何,我不确定您在正式规格中阅读了多少,但是您正在沿着这种思路读书。大卫·格里斯(David Gries)的书是该主题的经典书籍,我仍然没有足够掌握该概念,无法在日常编程中迅速使用它。对正式规格的通常反应是,它的艰苦而复杂,只有在您制定安全关键系统时才值得付出努力。但是我认为,信封技术的背面类似于可以使用的QuickCheck。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top