Skip to main content
 首页 » 编程设计

python之使用 assertRaises之处理传播的异常

2025年05月04日7hnrainll

我有一些代码,我正在测试一个包装异常,当它失败并且异常传播时,我认为错误消息和回溯不够详细,主要是因为它没有告诉我预期与. 测试,我想要异常和期望的详细信息。

我调整了我的测试(参见下面的示例代码)。我想知道这种方法是否有效,是否有任何 Python 测试或模拟框架允许直接实现它? (目前我正在使用 unittest 和 mox)

this question 的答案之一简要介绍了在这种情况下使用 self.fail 的适当性,但并未详细说明。我的假设是,如果我尝试将测试限制在一个区域,我可以通过测试。

注意:如果您运行该代码示例,它应该会失败,以演示我希望看到的行为。我正在使用 Python 2.7、Mox 0.5.3

import sys 
import urllib2 
from contextlib import closing 
 
try: 
    import lxml.etree as ET 
except ImportError: 
    import xml.etree.ElementTree as ET 
 
 
class Defect(Exception): 
    """Wrapped exception, for module error detection""" 
    def __init__(self, *args): 
        Exception.__init__(self, *args) 
        self.wrapped_exc = sys.exc_info() 
 
 
class StudioResources: 
    """Dummy class""" 
    def _opener(self, request, html=False): 
        with closing(urllib2.urlopen(request)) as response: 
            try: 
                if html: 
                    import lxml.html 
                    return lxml.html.parse(response) 
                else: 
                    return ET.parse(response) 
            except urllib2.HTTPError, e: 
                if e.code in [400, 500]: # Bad Request, Internal Server Error 
                    raise Defect, "report error to the library maintainer" 
                else: 
                    raise 
 
 
### 
# Tests 
### 
import unittest 
import mox 
import traceback 
import difflib 
import urllib 
import httplib 
 
 
def format_expectation(exc_expected=None, exc_instance=None): 
    """Synopsis - For exceptions, inspired by _AssertRaisesContext 
 
    try: 
        self.assertRaises(myexc, self.studio._opener, None) 
    except Exception, e: 
        self.fail(format_expectation(exc_expected=myexc, exc_instance=e)) 
    """ 
    if not isinstance(exc_expected, type) or exc_instance is None: 
        raise ValueError, "check __init__ args" 
 
    differ = difflib.Differ() 
    inst_class = exc_instance.__class__ 
    def fullname(c): return "%s.%s" % (c.__module__, c.__name__) 
    diff = differ.compare( 
        (fullname(inst_class),), (fullname(exc_expected),)) 
    _str = ("Unexpected Exception type.  unexpected:-  expected:+\n%s" 
        % ("\n".join(diff),)) 
    return _str 
 
 
class StudioTest(mox.MoxTestBase): 
    def setUp(self): 
        mox.MoxTestBase.setUp(self) 
        self.studio = StudioResources() 
 
    def test_opener_defect(self): 
        f = urllib.addinfourl(urllib2.StringIO('dummy'), None, None) 
        RESP_CODE = 501 
        self.mox.StubOutWithMock(f, 'read') 
        self.mox.StubOutWithMock(urllib2, 'urlopen') 
        urllib2.urlopen(mox.IgnoreArg()).AndReturn(f) 
        f.read(mox.IgnoreArg()).AndRaise(urllib2.HTTPError( 
            'http://c.com', RESP_CODE, httplib.responses[RESP_CODE], "", None)) 
        self.mox.ReplayAll() 
        try: 
            with self.assertRaises(Defect) as exc_info: 
                self.studio._opener(None) 
        except Exception, e: 
            traceback.print_exc() 
            self.fail(format_expectation(exc_expected=Defect, exc_instance=e)) 
        # check the response code 
        exc, inst, tb = exc_info.exception.wrapped_exc 
        self.assertEquals(inst.code, RESP_CODE) 
        self.mox.VerifyAll() 
 
 
if __name__ == '__main__': 
    unittest.main() 

请您参考如下方法:

在编写单元测试时,将测试限制在一件事上总是一个好主意。我没有发现您的代码有任何问题,但我会将所有内容包装在上下文管理器中。我使用 nose 而不是 unittest,它将任何 AssertionError 视为失败(这意味着无需调用 self.fail()),并且我编写了自己的上下文管理器来处理此问题案件。如果您有兴趣,这是代码:

class assert_raises: 
 
    def __init__(self, exception): 
        self.exception = exception 
 
    def __enter__(self): 
        return self 
 
    def __exit__(self, exc_type, exc_val, exc_tb): 
        assert exc_type is self.exception, "Got '{}', expected '{}'"\ 
            .format('None' if exc_type is None else exc_type.__name__, 
                    self.exception.__name__) 
        return True 

然后在这些示例中使用它:

>>> with assert_raised(ValueError): 
...    raise ValueError 
 
>>> with assert_raised(ValueError): 
...    pass 
Traceback (most recent call last): 
    ... 
AssertionError: Got 'None', expected 'ValueError' 
 
>>> with assert_raised(ValueError): 
...     raise TypeError 
Traceback (most recent call last): 
    ... 
AssertionError: Got 'TypeError', expected 'ValueError' 

由于引发了 AssertionError,nose 将其视为失败并打印完整的回溯。这是为 nose 设计的,但是为 unittest 和 mox 调整它是一件微不足道的事情。如果您不太关心失败的确切模式,您甚至可以按原样使用它。