作为一名程序员,我全心全意地接受 TDD 哲学,并努力为我编写的任何重要代码进行广泛的单元测试。有时这条路可能会很痛苦(行为变化导致级联多个单元测试更改;需要大量的脚手架),但总的来说,我拒绝在没有测试的情况下进行编程,每次更改后我都可以运行,而且我的代码作为结果。
最近,我一直在玩 Haskell,它是常驻测试库 QuickCheck。以一种与 TDD 截然不同的方式,QuickCheck 强调测试代码的不变量,即包含所有(或实质性子集)输入的某些属性。一个简单的例子:如果我们运行两次,一个稳定的排序算法应该给出相同的答案,应该有增加的输出,应该是输入的排列等等。然后,QuickCheck 生成各种随机数据以测试这些不变量。
在我看来,至少对于纯函数(即没有副作用的函数——如果你正确地模拟,你可以将脏函数转换为纯函数),不变测试可以取代单元测试作为这些功能的严格超集. 每个单元测试都由一个输入和一个输出组成(在命令式编程语言中,“输出”不仅是函数的返回,还包括任何更改的状态,但这可以被封装)。可以想象创建一个随机输入生成器,它足以覆盖您手动创建的所有单元测试输入(然后是一些,因为它会生成您不会想到的案例);如果由于某些边界条件而在程序中发现错误,则可以改进随机输入生成器,以便它也生成这种情况。
那么,挑战在于是否有可能为每个问题制定有用的不变量。我想说的是:一旦你有一个答案来看看它是否正确,这比首先计算答案要简单得多。考虑不变量也有助于阐明复杂算法的规范,这比 ad hoc 测试用例更好,后者鼓励对问题进行逐个案例的思考。您可以使用以前版本的程序作为模型实现,或者使用另一种语言的程序版本。等等。最终,您可以覆盖所有以前的测试用例,而无需显式编码输入或输出。
我是不是疯了,还是我在做某事?