2012-01-21 72 views
2

我有一些代碼在測試包裝的異常,當它失敗並且異常傳播時我認爲錯誤消息和反向跟蹤不夠詳細,主要是因爲它沒有告訴我對測試有什麼期望,我想知道例外和期望的細節。使用assertRaises - 處理傳播異常

我調整了我的測試(請參閱下面的示例代碼)。我想知道這種類型的方法是否有效,以及任何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() 

回答

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,鼻子把它看作是一個失敗並打印出完整的回溯無論如何。這是專門爲鼻子設計的,但用unittest和mox來調整它是件微不足道的事情。如果您不太在意確切的失敗模式,您甚至可以按原樣使用它。

+0

剛看到你的答案 - 謝謝! 我特別喜歡使用Context Manager;我認爲這更符合Pythonic - 更符合我最初的期望。 –