4

我最近遇到了一个简单但讨厌的错误。 我有一个列表,我想找到它中最小的成员。我使用Python的内置min()。 一切工作很好,直到在一些奇怪的情况下列表是空的(由于奇怪的用户输入,我不能预料)。我的应用程序崩溃了ValueError(顺便说一句 - 没有在官方文档中记录)。Python在覆盖失败时对异常进行预测试

我有非常广泛的单元测试,我经常检查覆盖范围,以避免这样的惊喜。我也使用Pylint(所有东西都集成在PyDev中),我从不忽略警告,但是在我的用户之前我没有发现这个错误。

有没有什么我可以改变在我的方法避免这些类型的运行时错误? (这在编译时会被Java/C#捕获?)。

我正在寻找一些东西,而不是用大的尝试来包装我的代码。我还可以做些什么?有多少其他的Python函数构建隐藏这样的令人讨厌的惊喜?

+0

要澄清一点:我不认为最小/最大应该返回None或0.在这种情况下,他们提出异常是有道理的。我只是担心我只是通过我的sw发现了一条隐藏的路线,我并不知道也没有测试过......而且代码与用户远离:这是一个自然语言旅行请求我提取所有数据。在某些时候,我试图从文本中找到100公里半径范围内最大的城市,但是用户进入了澳大利亚的一个机场,并在其旁边有0个城市......我的不好,当然...... – 2010-04-15 20:59:59

+0

潜在答案:如果我没有将它传递给空列表导致它引发异常,请改进覆盖率以显示此行的部分覆盖范围!这样它会在覆盖报告中弹出,我会写一个测试来覆盖它。这是可行的吗? – 2010-04-16 06:07:27

回答

5

即使在Java/C#中,RuntimeError也没有被选中,编译器也不会检测到这种异常(这就是为什么它们被称为RuntimeError而不是CompileError)的原因。

在python中,某些异常(如KeyboardInterrupt)特别毛茸茸,因为它可以在程序中的任意点上实际引发。

我正在寻找一些东西,而不是用大的尝试来包装我的代码 - 除了。

任何事情,但请。让异常得到用户并停止程序,而不是让错误静静地传递(Python的禅)是更好的。

与Java不同,Python不需要捕获所有异常,因为要求捕获所有异常使得程序员很容易忽略异常(通过编写空白异常处理程序)。

只是放松,让错误停止;让用户向你报告,这样你就可以修复它。另一种选择是你在四十二小时内进入一个调试器,因为由于空白的强制性异常处理程序,客户的数据正在被破坏。

所以,你应该改变你的方法是认为异常是坏的;他们并不漂亮,但他们比替代品更好。

+0

我同意100%,这是我使用的方法。但 - 我会喜欢有一个覆盖工具,让我运行我的单元测试,然后告诉我,我从来没有经历过我的代码的某些路径(例如这个例外)。 – 2010-08-13 12:40:40

0

我不知道你的问题的直接答案;如果pylint警告这种可能性,我也会喜欢它。由于空列表在各种情况下都会造成问题,我的一般做法是在使用之前查看真相清单;例如:

val = min(vals) if vals else 0 

在许多情况下,这是“免费”的,因为你经常需要检查None反正。它还可以对特殊情况下的空列表进行性能优化,以避免即开始新的线程,进程或数据库事务来处理零个项目。

+1

没有downvoting,但如果列表是[[1,0,3,2,5]'?您现在没有办法(在您的构造中)区分有效的最小值和由空列表引起的值。 – ChristopheD 2010-04-15 18:44:44

+0

@ChristopheD:这就是主意;经常,尤其是在这里用* min *,空或者甚至没有列表不是真的有问题。在这种情况下,经常会有一些值,比如零。我只是在编写代码时尽量了解这一点,就像我在处理None时一样。不幸的是,“更加警惕”仅仅是迄今为止,所以我承认这不是对这个问题的真实答案。 – DNS 2010-04-15 18:53:50

+1

有关于将哨兵添加到最小/最大http://bugs.python.org/issue7153的讨论,但由于各种原因他们被拒绝。 – 2010-04-15 19:03:50

7

这里的问题是格式错误的外部输入使程序崩溃。解决方案是彻底地在代码边界单元测试可能的输入场景。你说你的单元测试是'广泛的',但你显然没有测试过这种可能性。代码覆盖率是一个有用的工具,但重要的是要记住覆盖代码是而不是,就像彻底测试它一样。彻底的测试是涵盖使用场景以及代码行的组合。

我使用的方法是信任内部呼叫者,但从不信任外部呼叫者或输入。所以我明确不要单元测试的空列表情况下超出第一个函数接收外部输入的任何代码。但是那个输入函数应该是穷举覆盖。

在这种情况下,我认为图书馆的例外是合理的行为 - 要​​求min是空的列表是没有意义的。例如,图书馆无法为您合理设置一个值为0的值,因为您可能正在处理负数。

我认为空列表应该永远不会到达请求min的代码 - 它应该在输入时被识别出来,并且在那里引发异常,或者将其设置为0(如果这对您有用)或者其他任何其他这是对你有用。

+0

关于处理自然语言的有趣之处在于,你无法在任何地方尽力覆盖潜在的投入。这是我必须面对的现实。我对图书馆的行为没有任何问题(除了没有记录的事实!)。 – 2010-08-13 12:36:45

1

您也可以使用随机测试:

#!/usr/bin/env python 
import random 
from peckcheck import TestCase, an_int, main 

def a_seq(generator): 
    return lambda size: [generator(size) 
         for _ in xrange(random.randrange(size))] 

class TestMin(TestCase): 
    def testInputNoThrow(self, x=a_seq(an_int)): 
     min(x) 

if __name__=="__main__": 
    main() 

要安装peckcheck,类型:

$ pip install http://github.com/downloads/zed/peckcheck/peckcheck-0.1.v2.6.tar.gz 

或者只是grub的peckcheck.py

+0

我真的不是这个粉丝,因为你的测试依赖于随机性。您可以通过提供种子来使用伪随机性:http://stackoverflow.com/questions/9023660/how-to-generate-a-repeatable-random-number-sequence – seanp2k 2014-06-09 21:28:56

+0

@ seanp2k:您错过了随机测试的要点。如果你想要一个确定性测试(*除了随机测试*),那么你也可以硬编码测试的输入。 – jfs 2014-06-10 11:33:15