如何用Python Nose测试自定义异常和消息
我自定义的异常类:
class MyCustomException(Exception):
pass
class MyCustomRestException(MyCustomException):
def __init__(self, status, uri, msg=""):
self.uri = uri
self.status = status
self.msg = msg
super(MyCustomException, self).__init__(msg)
def __str__(self):
return "HTTP ERROR %s: %s \n %s" % (self.status, self.msg, self.uri)
我的测试
# note: @raises(MyCustomRestException) works by itself
@raises(MyCustomRestException, 'HTTP ERROR 403: Invalid User')
def test_bad_token():
sc = SomeClass('xxx', account_number)
result = ec.method_that_generates_exception()
这是nose返回的结果
12:52:13 ~/sandbox/ec$ nosetests -v
Failure: AttributeError ('str' object has no attribute '__name__') ... ERROR
======================================================================
ERROR: Failure: AttributeError ('str' object has no attribute '__name__')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/nose/loader.py", line 390, in loadTestsFromName
addr.filename, addr.module)
File "/usr/local/lib/python2.7/site-packages/nose/importer.py", line 39, in importFromPath
return self.importFromDir(dir_path, fqname)
File "/usr/local/lib/python2.7/site-packages/nose/importer.py", line 86, in importFromDir
mod = load_module(part_fqname, fh, filename, desc)
File "/ec/tests/my_client_tests.py", line 16, in <module>
@raises(MyCustomRestException, 'HTTP ERROR 403: Invalid User')
File "/usr/local/lib/python2.7/site-packages/nose/tools/nontrivial.py", line 55, in raises
valid = ' or '.join([e.__name__ for e in exceptions])
AttributeError: 'str' object has no attribute '__name__'
----------------------------------------------------------------------
Ran 1 test in 0.012s
FAILED (errors=1)
那么……
我有两个问题:
- 我该如何修复这个错误?
- 我该如何测试(单独测试或一起测试):
- 异常类型
- 异常状态
- 异常URI
- 异常消息
解决方案:在alynna的帮助下(如下)
这个方法很好用。
def test_bad_token():
sc = SomeClass('xxx', account_number)
with assert_raises(MyCustomRestException) as e:
sc.method_that_generates_exception()
assert_equal(e.exception.status, 403)
assert_equal(e.exception.msg, 'Invalid User')
2 个回答
1
Nose的@raises
装饰器只能检查异常的类型,不能检查其他的东西。如果你传入更多的参数,意味着你想允许更多类型的异常被视为有效的。这时候,Nose会把你传入的字符串当作异常来处理,但找不到它的__name__
属性。(具体可以参考文档)
要解决这个问题,你可以为你的自定义异常实现一个额外的装饰器,像这样:
from nose.tools import eq_
from functools import wraps
def raises_custom(status=None, uri=None, msg=None):
assert status or uri or msg, 'You need to pass either status, uri, or msg'
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
try:
function(*args, **kwargs)
except MyCustomException, e:
def check(value, name):
if value:
eq_(getattr(exception, name), value)
check(status, 'status')
check(uri, 'uri')
check(msg, 'msg')
except:
raise
else:
message = "%{} did not raise MyCustomException".format(\
function.__name__)
raise AssertionError(message)
return wrapper
return decorator
然后你可以像这样使用它:@raises_custom(msg="HTTP ERROR 403: Invalid User")
。
(我没有测试上面的代码,只是想给出一个大概的样子)
更新:使用assertRaises,正如alynna所建议的,可能会更简洁一些。特别是如果你能明确指出异常应该发生的具体位置,那样会更好,而不是把整个函数都用装饰器包裹起来。
2
我觉得你的问题在于,@raises 装饰器的参数应该都是异常类。你可以查看这个链接了解更多:https://nose.readthedocs.org/en/latest/testing_tools.html#nose.tools.raises
你可能更想用 assertRaises。文档里有说明它是用来测试异常的额外属性的,具体可以看这里:http://docs.python.org/2/library/unittest.html#unittest.TestCase.assertRaises