如何在 Python 2.7的 unittest 中显示 assertRaises()捕获的错误消息?

为了确保来自我的模块的错误消息具有信息性,我希望看到 assertRaises ()捕获的所有错误消息。今天,我为每个 assertRaises ()都这样做,但是由于在测试代码中有很多这样的代码,这样做变得非常单调乏味。

如何打印所有 assertRaises ()的错误消息?我研究了关于 http://docs.python.org/library/unittest.html的文档,但是没有找到解决它的方法。我能以某种方式修补 assertRaises ()方法吗?我不喜欢更改测试代码中的所有 assertRaises ()行,因为我通常以标准方式使用测试代码。

我想这个问题和 Python unittest: 如何在 Exception 中测试参数?有关

我今天就是这么做的,比如:

#!/usr/bin/env python


def fail():
raise ValueError('Misspellled errrorr messageee')

测试代码:

#!/usr/bin/env python
import unittest
import failure


class TestFailureModule(unittest.TestCase):


def testFail(self):
self.assertRaises(ValueError, failure.fail)


if __name__ == '__main__':
unittest.main()

要检查错误消息,我只需将 assertRaises ()中的错误类型更改为(例如 IOError)。然后我可以看到错误消息:

 E
======================================================================
ERROR: testFail (__main__.TestFailureModule)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_failure.py", line 8, in testFail
self.assertRaises(IOError, failure.fail)
File "/usr/lib/python2.7/unittest/case.py", line 471, in assertRaises
callableObj(*args, **kwargs)
File "/home/jonas/Skrivbord/failure.py", line 4, in fail
raise ValueError('Misspellled errrorr messageee')
ValueError: Misspellled errrorr messageee


----------------------------------------------------------------------
Ran 1 test in 0.001s


FAILED (errors=1)

有什么建议吗? 乔纳斯

编辑:

在罗伯特 · 罗斯尼的提示下,我设法解决了这个问题。它主要不是用于拼写错误,而是用于确保错误消息对模块的用户真正有意义。通过设置 SHOW _ ERROR _ MESSAGES = False,可以实现 unittest 的正常功能(这是我大多数时候使用它的方式)。

我只是重写 assertRaises ()方法,如下所示!

SHOW_ERROR_MESSAGES = True


class NonexistantError(Exception):
pass


class ExtendedTestCase(unittest.TestCase):
def assertRaises(self, excClass, callableObj, *args, **kwargs):
if SHOW_ERROR_MESSAGES:
excClass = NonexistantError
try:
unittest.TestCase.assertRaises(self, excClass, callableObj, *args, **kwargs)
except:
print '\n    ' + repr(sys.exc_info()[1])

产出的一小部分:

testNotIntegerInput (__main__.TestCheckRegisteraddress) ...
TypeError('The registeraddress must be an integer. Given: 1.0',)


TypeError("The registeraddress must be an integer. Given: '1'",)


TypeError('The registeraddress must be an integer. Given: [1]',)


TypeError('The registeraddress must be an integer. Given: None',)
ok
testCorrectNumberOfBytes (__main__.TestCheckResponseNumberOfBytes) ... ok
testInconsistentLimits (__main__.TestCheckNumerical) ...
ValueError('The maxvalue must not be smaller than minvalue. Given: 45 and 47, respectively.',)


ValueError('The maxvalue must not be smaller than minvalue. Given: 45.0 and 47.0, respectively.',)
ok
testWrongValues (__main__.TestCheckRegisteraddress) ...
ValueError('The registeraddress is too small: -1, but minimum value is 0.',)


ValueError('The registeraddress is too large: 65536, but maximum value is 65535.',)
ok
testTooShortString (__main__.TestCheckResponseWriteData) ...
ValueError("The payload is too short: 2, but minimum value is 4. Given: '\\x00X'",)


ValueError("The payload is too short: 0, but minimum value is 4. Given: ''",)


ValueError("The writedata is too short: 1, but minimum value is 2. Given: 'X'",)


ValueError("The writedata is too short: 0, but minimum value is 2. Given: ''",)
ok
testKnownValues (__main__.TestCreateBitPattern) ... ok
testNotIntegerInput (__main__.TestCheckSlaveaddress) ...
TypeError('The slaveaddress must be an integer. Given: 1.0',)


TypeError("The slaveaddress must be an integer. Given: '1'",)


TypeError('The slaveaddress must be an integer. Given: [1]',)


TypeError('The slaveaddress must be an integer. Given: None',)
ok
88022 次浏览

Out-of-the-box unittest doesn't do this. If this is something you want to do frequently, you can try something like this:

class ExtendedTestCase(unittest.TestCase):


def assertRaisesWithMessage(self, msg, func, *args, **kwargs):
try:
func(*args, **kwargs)
self.assertFail()
except Exception as inst:
self.assertEqual(inst.message, msg)

Derive your unit test classes from ExtendedTestCase instead of unittest.TestCase.

But really, if you're simply concerned about misspelled error messages, and concerned enough to want to build test cases around it, you shouldn't be inlining messages as string literals. You should do with them what you do with any other important strings: defining them as constants in a module that you import and that someone is responsible for proofreading. A developer who misspells words in his code will also misspell them in his test cases.

I once preferred the most excellent answer given above by @Robert Rossney. Nowadays, I prefer to use assertRaises as a context manager (a new capability in unittest2) like so:

with self.assertRaises(TypeError) as cm:
failure.fail()
self.assertEqual(
'The registeraddress must be an integer. Given: 1.0',
str(cm.exception)
)

You are looking for assertRaisesRegex, which is available since Python 3.2. From the docs:

self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$",
int, 'XYZ')

with self.assertRaisesRegex(ValueError, 'literal'):
int('XYZ')

PS: if you are using Python 2.7, then the correct method name is assertRaisesRegexp.

mkelley33 gives nice answer, but this approach can be detected as issue by some code analysis tools like Codacy. The problem is that it doesn't know that assertRaises can be used as context manager and it reports that not all arguments are passed to assertRaises method.

So, I'd like to improve Robert's Rossney answer:

class TestCaseMixin(object):


def assertRaisesWithMessage(self, exception_type, message, func, *args, **kwargs):
try:
func(*args, **kwargs)
except exception_type as e:
self.assertEqual(e.args[0], message)
else:
self.fail('"{0}" was expected to throw "{1}" exception'
.format(func.__name__, exception_type.__name__))

Key differences are:

  1. We test type of exception.
  2. We can run this code both on Python 2 and Python 3 (we call e.args[0] because errors in Py3 don't have message attribute).

If you want the error message exactly match something:

with self.assertRaises(ValueError) as error:
do_something()
self.assertEqual(error.exception.message, 'error message')