我们应该如何使用nose测试异常?

36 投票
6 回答
24153 浏览
提问于 2025-04-17 04:30

我正在用nose测试异常。这里有个例子:

def testDeleteUserUserNotFound(self):
    "Test exception is raised when trying to delete non-existent users"
    try:
        self.client.deleteUser('10000001-0000-0000-1000-100000000000')
        # make nose fail here
    except UserNotFoundException:
        assert True

如果抛出了异常,assert语句会被执行;但如果没有抛出异常,assert语句就不会被执行。

有没有什么可以放在上面那行注释的地方,这样如果没有抛出异常,nose就会报告失败呢?

6 个回答

9
def testDeleteUserUserNotFound(self):
    "Test exception is raised when trying to delete non-existent users"
    try:
        self.client.deleteUser('10000001-0000-0000-1000-100000000000')
        assert False # <---
    except UserNotFoundException:
        assert True

在编程中,tryexcept的意思是,当你在try块里遇到错误时,程序会跳出这个块去执行except块里的代码。所以,如果你在try里写了assert False,而之前发生了错误,assert False就不会被执行。此外,except块执行完后,程序不会再回到try块里,所以你不需要担心会出现问题。

     ↓
(statements)
     ↓    exception
   (try) ↚──────────→ (except)
     ↓                   │
(statements) ←───────────┘
     ↓
13

我强烈推荐使用 assert_raisesassert_raises_regexp,这两个工具来自 nose.tools,它们的功能和 assertRaises 以及 assertRaisesRegexp 是一样的。这样你就可以在不使用 unittest.TestCase 类的测试中,享受到同样的功能。

我觉得 @raises 这个工具太简单粗暴了。下面的代码展示了这个问题:

from nose.tools import *

something = ["aaa", "bbb"]

def foo(x, source=None):
    if source is None:
        source = something
    return source[x]

# This is fine
@raises(IndexError)
def test1():
    foo(3)

# This is fine. The expected error does not happen because we made 
# a mistake in the test or in the code. The failure indicates we made
# a mistake.
@raises(IndexError)
def test2():
    foo(1)

# This passes for the wrong reasons.
@raises(IndexError)
def test3():
    source = something[2]  # This is the line that raises the exception.
    foo(10, source)  # This is not tested.

# When we use assert_raises, we can isolate the line where we expect
# the failure. This causes an error due to the exception raised in 
# the first line of the function.
def test4():
    source = something[2]
    with assert_raises(IndexError):
        foo(10, source)

test3 这个测试通过了,但并不是因为 foo 抛出了我们预期的异常,而是因为设置 foo 使用的数据时出错了,抛出了同样的异常。test4 展示了如何使用 assert_raises 来真正测试我们想要测试的内容。第一行的问题会导致 Nose 报错,然后我们可以重写测试,使得这一行可以真正测试我们想要的内容。

@raises 不能测试与异常相关的消息。当我抛出 ValueError 时,通常我希望带上一个有用的提示信息。这里有个例子:

def bar(arg):
    if arg:  # This is incorrect code.
        raise ValueError("arg should be higher than 3")

    if arg >= 10:
        raise ValueError("arg should be less than 10")

# We don't know which of the possible `raise` statements was reached.
@raises(ValueError)
def test5():
    bar(10)

# Yes, we're getting an exception but with the wrong value: bug found!
def test6():
    with assert_raises_regexp(ValueError, "arg should be less than 10"):
        bar(10)

使用 @raisestest5 会通过,但原因是错误的。test6 进行了更细致的测试,揭示出抛出的 ValueError 不是我们想要的那个。

51

nose 提供了一些工具,用于测试程序中的错误(就像 unittest 一样)。你可以试试这个例子(还可以了解其他工具,访问 Nose 测试工具

from nose.tools import *

l = []
d = dict()

@raises(Exception)
def test_Exception1():
    '''this test should pass'''
    l.pop()

@raises(KeyError)
def test_Exception2():
    '''this test should pass'''
    d[1]

@raises(KeyError)
def test_Exception3():
    '''this test should fail (IndexError raised but KeyError was expected)'''
    l.pop()

def test_Exception4():
    '''this test should fail with KeyError'''
    d[1]

我觉得这就是你想要的正确方法,因为它让你可以明确你期望或想要的错误类型。这样你就可以故意制造错误,看看是否能抛出正确的异常。然后你让 nose 来评估结果。(在单元测试中尽量少写逻辑!)

撰写回答