多简单才算简单到无法测试?
我相信你和我一样,也花了很多时间思考如何编写更好的软件。如果你曾经尝试过单元测试的最佳实践,你肯定在某个时候会问这个问题:简单到什么程度才算太简单而无法测试?
好吧,今天我在一个项目中发现了一个错误,它表明可能没有下限。看看这个:
此方法对 $pcIDs 的内容进行了一些检查,如果出现错误,则会抛出异常。很简单,对吧?当然。包括签名和括号在内,它只有 15 行代码。
你相信这段代码有错误吗?我给你个提示:
您看到问题了吗(慢慢来,我会等)。
等待...
等待...
等待...
等待...
等待...
明白了吗?需要更明显的提示吗?
发生了什么
看看发生了什么?我们希望 $pcID[0] 和 $pcID[1] 是输入数组中仅有的值。它们应该是大于 0 的整数,并且彼此不相等。乍一看,该方法似乎运行正常。但仔细检查后,您可能会发现 ValidateID::run() 返回一个布尔值,但这段代码并未检查返回值。相反,它假设如果值无效,ValidateID::run() 会抛出异常。
因此,validateProductCategoryIDs(array('cat', 'dog')); 不会抛出异常,这不是我们想要的行为。
单元测试最佳实践
那么,多简单才算简单到不需要测试呢?我开始逐渐接受这样的观点:几乎没有什么东西简单到不需要测试。
我喜欢一句话,但我不记得是谁写的/说的:
为您关心的任何行为编写测试。
确实如此。这跟一场关于医院计算机漏洞的讲座很契合(顺便说一句,这讲座很棒)。讲师们认为,医生和护士经常在计算机系统中输入错误的数据。其中一些错误甚至导致了病人的死亡。但对我们来说,重要的是——他们不知道自己犯了错误。一位护士用数字键盘输入输液泵的药物剂量时,犯了一个他们无法识别的错误,大约有4%的概率!这看起来错误率很高,不是吗?
那么,如果护士们在不自觉的情况下把简单的数字输入搞乱了,那么程序员在不自觉的情况下在代码中插入愚蠢错误的频率又是多少呢?我想答案可能是很多。
我在本文中展示的代码是经过精心编写的。作者为其编写了全面的手动测试计划,但没有进行单元测试。作者使用我们的代码审查清单审查了代码。之后,我也使用我们的代码审查清单审查了代码,并执行了手动测试计划。而且,在所有这些质量保证之后,我们仍然忽略了这个非常明显的错误。
那么我们今天是如何发现这个错误的呢?
我的同事为一个调用validateProductCategoryIDs()方法的方法编写了单元/集成测试。他实际上并没有注意到这个问题,但当我对他的新测试进行代码审查时,我注意到array('cat', 'dog')类型的输入没有引发异常,这让我觉得很可疑。
总而言之,我们需要结合自动化测试和代码审查来捕获此错误。尽管这是一个简单的错误,但仅靠手动测试、单元测试或代码审查都不足以引起我们的注意。我们只能通过结合多种质量保证活动才能发现此错误。
顺便说一句,这正是史蒂夫·麦康奈尔 (Steve McConnell) 在《Code Complete》中所推荐的——多种 QA,因为每种 QA 都能捕获不同类型的错误。
关于整洁编码实践的几点思考
我们永远无法确切知道最初编写此代码的程序员的想法,但我可以自信地告诉你,这不是一个故意的错误。
如果你稍微分析一下根本原因,就会注意到我展示的代码中有两个验证方法。一个返回 void 或抛出异常,另一个返回 bool。这真是让人困惑。
对于返回不兼容类型的方法,不要使用容易混淆的命名系统
如果validateXXX方法返回的类型不兼容,那很可能就是代码异味。而且我敢打赌,这导致我们在代码审查期间没能发现这个错误。
我们或许可以将一些validateXXX方法的名称改为“verifyXXX”,以降低将来出现类似错误的可能性。这样可以明确地表明validate方法返回bool值,而verify方法在遇到不合适的数据时会抛出异常。我们的IDE拥有强大的重构支持,因此我们可以快速安全地进行此类更改。
通过静态分析检查发现这种潜在错误
我更倾向于使用集成到 IDE 中的静态分析检查,它可以帮助我们在编码时发现所有此类问题的实例。我不确定在我们的项目中启用这种检查有多容易,但我相信您一定能体会到它的吸引力。
总结
即使是最优秀的程序员也会犯错。通过仔细的代码审查和良好的代码整洁之道,你或许能够发现一些错误。但如果你想真正降低缺陷率,就需要结合多种质量保证方法。你的单元测试最佳实践应该涵盖所有你关注的行为,即使是那些看似简单到不会出错的代码。
同意还是不同意?我很想在评论区听到你的想法。
喜欢这篇文章吗?请在下方点赞。
鏂囩珷鏉ユ簮锛�https://dev.to/bosepchuk/how-simple-is-too-simple-to-test-2lh7