我们检查的Python代码库中有3%的单元测试失败

2022-02-17 11:27:44

薛定谔单元测试:一种通过但未能测试我们想要测试的东西的单元测试。

本文重点介绍我们对666个Python代码库的代码扫描,以检测这样一个薛定谔单元测试。具体来说,这个问题(我们在20个代码库中找到了它,后来推出了一个自动生成的修复程序):

你看到错误了吗?对于开发者来说,这是一个相对常见的错误。assertEqual和assertTrue被搞糊涂了。事实上,在我们检查的666个代码基中,我们发现有3%的代码基将两者混淆了。这是一个真正的问题,它会影响真正的代码库,甚至是具有巨大社区和强大测试和代码审查实践的活跃大型代码库:

请参阅本要点中的完整列表。这个问题如此普遍的事实表明,这个错误的一个关键原因是,在代码审查期间很容易漏掉它。快速浏览代码不会引起任何危险。它“看起来不错!”。

在19世纪末,马克·吐温显然说过,不是你所知道的让你着迷。在19世纪末的文献中是正确的,在21世纪初的Python单元测试中也是正确的:没有真正测试我们想要测试的东西的测试比根本没有测试更糟糕,因为薛定谔的测试会让人毫无根据地相信该特性是安全的。

在代码评审期间,该变更已被同行评审并接受。(“LGTM!”)

然后将更改部署到prod…但在第一次遇到用户时,它就失败了

在重新审视单元测试时,我们注意到,在软件开发生命周期中给予如此大信心的单元测试实际上根本没有测试功能。如果您遇到了这个问题,那么您就遇到了这类单元测试,这些单元测试是针对这个特定的测试用例的。assertTrue是其中之一。

虽然pytest非常流行,但28%的代码库仍然使用内置的unittest包。使用内置unittest包的代码库存在assertTrue gotcha的风险:

assertTrue的文档声明测试expr为真,但这是一个不完整的解释。实际上,它测试expr是否真实,这意味着如果将以下任何值用作第一个参数,测试将通过:

assertTrue还接受第二个参数,这是显示第一个参数是否为truthy的自定义错误消息。这个调用签名允许犯错误,测试通过,因此可能会无声地失败。

例如,在制作20个PRs修复问题时,我们发现,一旦测试不再是薛定谔单元测试,其中一个测试由于应用程序逻辑错误而开始失败:

我们还发现,至少有两个测试失败,因为测试中的逻辑是错误的。如果我们把单元测试看作是描述产品如何工作的一种文档形式,这也是不好的。从开发人员的角度来看,单元测试可以被认为比注释和文档字符串更可信,因为注释和文档字符串是编写的声明(可能很久以前,也可能现在已经过时或不完整),而通过的单元测试表明逻辑契约得到了维护……只要测试不是薛定谔测试!

也许需要更多的数据,但这可能意味着薛定谔的单元测试中有15%(20分之3)隐藏了损坏的功能。

当代码审查时。doctor github bot在github pull请求中检测到此错误,建议使用以下解决方案: