今天有一個需求, 在單元測試失敗的時候打印一些日誌, 咱們管他叫 dosomething 吧 ,反正就是作一些操做
查了下並無查到相關的方法, 因而研究了一波unittest
的源碼python
發現了這個東西app
try: self._outcome = outcome with outcome.testPartExecutor(self): self.setUp() if outcome.success: outcome.expecting_failure = expecting_failure with outcome.testPartExecutor(self, isTest=True): testMethod() outcome.expecting_failure = False with outcome.testPartExecutor(self): self.tearDown() self.doCleanups() for test, reason in outcome.skipped: self._addSkip(result, test, reason) self._feedErrorsToResult(result, outcome.errors) if outcome.success: if expecting_failure: if outcome.expectedFailure: self._addExpectedFailure(result, outcome.expectedFailure) else: self._addUnexpectedSuccess(result) else: result.addSuccess(self) return result finally: result.stopTest(self) if orig_result is None: stopTestRun = getattr(result, 'stopTestRun', None) if stopTestRun is not None: stopTestRun() # explicitly break reference cycles: # outcome.errors -> frame -> outcome -> outcome.errors # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure outcome.errors.clear() outcome.expectedFailure = None # clear the outcome, no more needed self._outcome = None
其中重點關注下testMethod()
這個正是咱們執行的用例
因而去看了下testPartExecutor
用例失敗的處理是在這裏進行處理的單元測試
def testPartExecutor(self, test_case, isTest=False): old_success = self.success self.success = True try: yield except KeyboardInterrupt: raise except SkipTest as e: self.success = False self.skipped.append((test_case, str(e))) except _ShouldStop: pass except: exc_info = sys.exc_info() if self.expecting_failure: self.expectedFailure = exc_info else: self.success = False self.errors.append((test_case, exc_info)) # explicitly break a reference cycle: # exc_info -> frame -> exc_info exc_info = None else: if self.result_supports_subtests and self.success: self.errors.append((test_case, None)) finally: self.success = self.success and old_success
奈何, 他只是在self.errors
(其中self爲咱們測試類的一個實例)中加了點東西測試
因而對self.errors
進行觀察,發現及時用例是正常的,他依然由內容.
這..............因而我想到他最終是怎麼打出來失敗的log的
看到 上面代碼中的 self._feedErrorsToResult(result, outcome.errors)
因而找到了這個東西日誌
def _feedErrorsToResult(self, result, errors): for test, exc_info in errors: if isinstance(test, _SubTest): result.addSubTest(test.test_case, test, exc_info) elif exc_info is not None: if issubclass(exc_info[0], self.failureException): result.addFailure(test, exc_info) else: result.addError(test, exc_info)
從上面的代碼咱們能夠知道 若是 error
的第二項是None
那麼就是一個執行成功的用例,通過實驗並確認了這個事情code
如今知道了 unittest
是如何處理 失敗用例的了ip
def tearDown(self): errors = self._outcome.errors for test, exc_info in errors: if exc_info: # dosomething pass
unittest
沒有處理這個事情,那咱們魔改之因而有了下面這種方法ci
import sys import contextlib import unittest from unittest.case import SkipTest, _ShouldStop, _Outcome @contextlib.contextmanager def testPartExecutor(self, test_case, isTest=False): old_success = self.success self.success = True try: yield except Exception: try: # if error getattr(test_case, test_case._testMethodName).__func__._error = True raise except KeyboardInterrupt: raise except SkipTest as e: self.success = False self.skipped.append((test_case, str(e))) except _ShouldStop: pass except: exc_info = sys.exc_info() if self.expecting_failure: self.expectedFailure = exc_info else: self.success = False self.errors.append((test_case, exc_info)) # explicitly break a reference cycle: # exc_info -> frame -> exc_info exc_info = None else: if self.result_supports_subtests and self.success: self.errors.append((test_case, None)) finally: self.success = self.success and old_success _Outcome.testPartExecutor = testPartExecutor class MyTest(unittest.TestCase): def test_1(self): print("test_1") def test_2(self): print("test_2") raise ValueError def tearDown(self): if hasattr(getattr(self, self._testMethodName), "_error"): # dosomething pass # def tearDown(self): # 推薦這種方法 # errors = self._outcome.errors # for test, exc_info in errors: # if exc_info: # # dosomething # pass if __name__ == '__main__': unittest.main()
這樣咱們就能夠在用例執行失敗後在tearDown
的時候作一些操做get