本节书摘来自华章出版社《有效的单元测试》一书中的第2章,第2.5节,作者 (芬)Lasse Koskela,更多章节内容可以访问云栖社区“华章计算机”公众号查看
2.5 可靠的测试才是可靠的
前一节中我说过,有时候测试的内容与你想象的完全不同。更让人操心的是,有时它们根本什么都没测试。
我的一个同事习惯于称这种测试为快乐的测试,指某个测试快乐地执行一段生产代码——或许是全部的执行路径——却没有一句断言。是的,你的测试覆盖率报告看起来很棒,因为测试全面地执行了你写的每句话。问题是这种测试只有在生产代码抛出异常时才会失败。
你无法依靠这种测试来保护自己,对吗?特别是如果程序员惯于将所有测试方法体封装到try-catch块中的时候。代码清单2.2展示了这种坏习惯。
某些测试相对来说不太容易失败,代码清单2.2是一个典型的极端例子,其中的测试或许永远不会失败(过去也没有过)。如果你仔细观察,你会注意到即使add(-1)没有如期地抛出异常,测试也不会失败。
几乎不会失败的测试就等于废物。也就是说,间歇性地通过或失败的测试就是在公然地侵害程序员小伙伴们,见图2.3。
几年前,我为某个项目做咨询,花了大部分时间与客户的技术人员及其他顾问做结对编程。一天早上我和我的搭档接到一个新任务,然后像往常一样先运行一下相关的测试集。我的搭档对代码库相当熟悉,编写了其中大部分代码,也熟悉其中的各种怪异之处。在我们做出任何修改之前,我注意到一些测试在第一次运行时失败了。让我惊讶的是,我的搭档如此来对待失败的测试——他不断地一遍遍重复运行测试,直到四五次以后所有测试至少都通过了一次。我不是100%确定,但我不认为所有测试都是在同一次运行中通过的。
我目瞪口呆,我意识到我见到的一堆测试其实全都是不可靠的测试。某些测试会随机地失败,因为被测代码包含了不确定的逻辑,于是测试有50%的机会会失败。除了在被测代码中使用了伪随机数生成器之外,这种间歇性行为的另一个常见原因是使用了时间相关的API。我最喜欢调用System.currentTimeMillis(),紧随其后的就是在测试异步逻辑时无处不在的Thread.sleep(1000)。
为了让测试值得依靠,它们就需要可重复。如果运行两遍测试,它就必须给我相同的结果。否则,我就不得不在每次构建之后采取人工干预,因为无法知道1250/2492是意味着一切正常,还是说最后一遍时全都挂了。无法知道。
如果你的逻辑包含异步内容或依赖于当前时间,确保将它们隔离在一个接口之后,这样你可以用“测试替身”来替换它们从而使测试可重复——这是测试变得可靠的一个关键要素。