今天有一個需求, 在單元測試失敗的時候打印一些日志, 我們管他叫 dosomething 吧 ,反正就是做一些操作
查了下並沒有查到相關的方法, 於是研究了一波unittest 的源碼
發現了這個東西
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那么就是一個執行成功的用例,經過實驗並確認了這個事情
現在知道了 unittest 是如何處理 失敗用例的了
於是便有了下面這種方法
def tearDown(self):
errors = self._outcome.errors
for test, exc_info in errors:
if exc_info:
# dosomething
pass
上面這種方法盡量少的改變原來的邏輯, 想到一種新的方法解決問題
既然unittest沒有處理這個事情,那我們魔改之
於是有了下面這種方法
注意: 不建議魔改代碼
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的時候做一些操作
