如何在Python2.7中的unittest中显示由assertRaises()捕获的错误消息?


77

为了确保来自我的模块的错误消息是有用的,我想查看assertRaises()捕获的所有错误消息。今天,我对每个assertRaises()都这样做,但是由于测试代码中有很多代码,因此变得非常乏味。

如何打印所有assertRaises()的错误消息?我研究了http://docs.python.org/library/unittest.html上的文档,但没有弄清楚如何解决它。我可以以某种方式猴子断言assertRaises()方法吗?我宁愿不更改测试代码中的所有assertRaises()行,因为我最经常以标准方式使用测试代码。

我猜这个问题与Python unittest有关:如何在Exceptions中测试参数?

我今天就是这样做的。例如:

#!/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,可以实现单元测试的正常功能(这是我大多数时候的用法)。

我简单地覆盖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

4
如果需要检查参数,为什么还要继续使用assertRaises?为什么不简单地捕获异常并使用try 和检查它except呢?
S.Lott

Answers:


53

开箱即用unittest不会执行此操作。如果您想经常这样做,可以尝试如下操作:

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)

从而ExtendedTestCase不是从派生单元测试类unittest.TestCase

但是,实际上,如果您只是担心拼写错误的错误消息,并且足够担心要围绕它构建测试用例,则不应将消息内联为字符串文字。您应该对它们进行任何其他重要的字符串处理:将它们定义为您导入的模块中的常量,并由某人负责校对。拼写错误代码的开发人员也会在测试用例中拼错它们。


21
+1表示“开发人员在代码中拼写错误的单词也会在测试用例中拼写错误”。
Johnsyweb 2011年

17
对我来说,当您进行测试以查看正在发生特定的错误时,这要可怕得多,但是由于意外的副作用,测试可以“通过”。例如,您没有期望的错误没有引发,但是在其他地方引发了相同的错误类型,因此可以满足测试要求。测试通过,代码已错误。将错误归为要查找的错误的错误的处理方法相同-如果测试过于笼统,最终会发现意外内容。
马克·辛普森

1
您应该使用 inst.args[0] 而不是 inst.message 在Python 2和Python 3上运行此代码
oblalex 2014年

3
这是不正确的,开箱即用的unittest可以做到这一点。
乔纳森·哈特利

Neato的子类化示例。但是我很难相信这是必要的还是有利的,因为unittest“开箱即用”的功能非常广泛。
Tom Russell

121

我曾经喜欢@Robert Rossney给出的最出色的答案。如今,我更喜欢使用assertRaises作为上下文管理器(unittest2中的一项新功能),如下所示:

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

2
注意 assertRaises可以用作Python 2.7中“ unittest”的上下文管理器。unittest2向后移植了Python早期版本的功能。docs.python.org/2/library/...
powlo

如果,代码在“ with”部分失败。 .exception.faultCode,101001,“故障代码与预期故障代码%d'%101001不匹配”
Arindam Roychowdhury 2014年

@arindamroychowdhury,很抱歉,但是我已经有一段时间没有编码任何Python了,所以我不知道您问题的答案。祝你好运。也许,这里的其他人之一可以更好地回答您的问题。祝好运。
mkelley33

我正在使用Python 2.7。我必须用cm.exception.parameter替换str(cm.exception)。
Chuck

58

您正在寻找assertRaisesRegex,该版本自Python 3.2起可用。从文档:

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

要么:

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

PS:如果您使用的是Python 2.7,则正确的方法名称为assertRaisesRegexp


是的,但是如果未出现预期的错误,您将永远不会看到该消息/无法更改默认消息。在循环中测试某些参数时,非常不舒服-您不知道该函数在没有预期错误的情况下传递给哪个参数。
Mesco

2
在python 3.6上,是说DeprecationWarning: Please use assertRaisesRegex instead使用时with self.assertRaisesRegexp( RuntimeError, '...regex...' )
用户

33

如果您希望错误消息与某些内容完全匹配:

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

6
在我的情况下,我不得不使用str(error.exception)而不是error.exception.messageas,error.exception而没有任何message属性。
Rik Schoonbeek

6

mkelley33提供了很好的答案,但是可以通过某些代码分析工具(例如Codacy)将这种方法检测为问题。问题是它不知道assertRaises可以用作上下文管理器,并且报告并非所有参数都传递给assertRaises method

所以,我想改善罗伯特·罗斯尼的答案:

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__))

主要区别在于:

  1. 我们测试异常的类型。
  2. 我们可以在Python 2和Python 3上运行此代码(e.args[0]之所以调用,是因为Py3中的错误没有 message属性)。

这是IMO的非常优雅的解决方案。如果您不喜欢e.args[0],也可以调用str(e)
Laryx Decidua
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.