with self.assertRaises(TypeError) as cm: do_something() exception = cm.exception self.assertEqual(exception.error_code, 3)我覺得新介面用 with 執行, 既易懂又能彈性地執行不同行數的程式。想不到它怎麼做出這樣的行為。好奇之下, 看了一下原始碼, 才發覺作法很簡單:
def assertRaises(self, excClass, callableObj=None, *args, **kwargs): # ... if callableObj is None: return _AssertRaisesContext(excClass, self) # ...
class _AssertRaisesContext(object): """A context manager used to implement TestCase.assertRaises* methods.""" def __init__(self, expected, test_case, expected_regexp=None): self.expected = expected self.failureException = test_case.failureException self.expected_regexp = expected_regexp def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): if exc_type is None: try: exc_name = self.expected.__name__ except AttributeError: exc_name = str(self.expected) raise self.failureException( "%s not raised" % (exc_name,)) # ...
就是傳回一個物件 _AssertRaisesContext, 讓該物件實作 with 的 __enter__ 和 __exit__, __exit__ 的參數含有過程中產生的 exception 相關資訊, 離開時檢查它就知道有沒有產生 exception 了。
另外 unittest2 為了和 unittest 相容, 介面寫得挺漂亮的, unittest2 的 assertRaises 會先依參數數量判斷是舊的呼叫方式還是新的使用 with 的呼叫方式, 再做對應的處理。需要維護的程式變多後, 對於向下相容的藝術, 才有進一步的體會。