【问题标题】:using assertRaises - handling propagated exceptions使用 assertRaises - 处理传播的异常
【发布时间】:2012-02-15 18:17:29
【问题描述】:

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

我调整了我的测试(参见下面的示例代码)。我想知道这种方法是否有效,是否有任何 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()

【问题讨论】:

    标签: python unit-testing tdd expectations assertraises


    【解决方案1】:

    在编写单元测试时,将测试限制为一件事总是一个好主意。我看不出您的代码有任何问题,但我会将所有内容都包含在上下文管理器中。我使用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 将其视为失败并打印完整的回溯。这是为鼻子设计的,但是将其调整为 unittest 和 mox 将是一件小事。如果您不太关心失败的确切模式,您甚至可以按原样使用它。

    【讨论】:

    • 刚刚看到您的回答 - 谢谢!我特别喜欢使用上下文管理器;在我看来,它更像 Pythonic - 更符合我最初的期望。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多