今天有一个需求, 在单元测试失败的时候打印一些日志, 我们管他叫 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的时候做一些操作
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理