Python在覆盖失败时的异常预测试

4 投票
4 回答
1006 浏览
提问于 2025-04-15 21:39

我最近遇到了一个简单但很棘手的错误。我有一个列表,想要找出里面最小的数字。我用了Python自带的min()函数。一切都运行得很好,直到在某个奇怪的情况下,列表变成了空的(这是因为用户输入了一些我没法预料到的奇怪内容)。我的应用程序因此崩溃了,报了一个ValueError(顺便说一下,这个错误在官方文档里没有说明)。

我有非常全面的单元测试,并且我定期检查代码覆盖率,以避免出现这样的意外。我还使用Pylint(这些都集成在PyDev里),我从来不忽视警告,但我还是没能在用户之前发现这个错误。

我可以在我的方法上做些什么改变,以避免这种运行时错误吗?(在Java或C#中,这种错误会在编译时被捕捉到)。

我希望找到一些比用一个大大的try-except包裹我的代码更好的解决办法。我还能做些什么呢?Python里还有多少其他内置函数也藏着这样的意外呢???

4 个回答

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

4

即使在Java或C#中,有一类叫做运行时错误(RuntimeError)的异常是不会被编译器检查的(这就是它们叫运行时错误而不是编译错误的原因)。

在Python中,有些异常,比如键盘中断(KeyboardInterrupt),特别麻烦,因为它可以在程序的任何地方被触发。

我希望能找到比把我的代码包裹在一个大大的try-except里更好的解决办法。

请不要这样做。让异常直接反馈给用户并停止程序,比让错误悄悄地存在要好得多(这也是Python的哲学之一)。

与Java不同,Python并不要求捕获所有异常,因为如果强制捕获所有异常,程序员就很容易忽视这些异常(比如写一个空的异常处理器)。

放轻松,让错误停止运行;让用户向你报告,这样你才能修复它。否则,你可能会花上四十二个小时去调试,因为客户的数据因为一个空的强制异常处理器而到处被破坏。

所以,你需要改变的思维方式是,不要把异常看作坏事;虽然它们不好看,但总比其他选择要好。

7

这里的问题是,外部输入格式不正确导致你的程序崩溃。解决办法是对代码边界可能出现的输入情况进行全面的单元测试。你说你的单元测试很“全面”,但显然你没有测试到这种情况。代码覆盖率是一个有用的工具,但要记住,覆盖代码并不等于彻底测试代码。彻底测试是覆盖使用场景和代码行的结合。

我使用的方法是信任内部调用者,但绝不信任外部调用者或输入。因此,我在任何接收外部输入的函数之外,明确对空列表的情况进行单元测试。但这个输入函数应该被全面测试。

在这种情况下,我认为库抛出的异常是合理的行为——请求一个空列表的min是没有意义的。这个库不能随便给你设置一个值,比如0,因为你可能在处理负数。

我认为空列表不应该到达请求min的那段代码——它应该在输入时就被识别出来,并在那时抛出异常,或者如果你觉得合适,可以将其设置为0,或者其他任何适合你的处理方式。

撰写回答