python 单元测试中处理用例失败的情况

今天有一个需求, 在单元测试失败的时候打印一些日志, 我们管他叫 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的时候做一些操作

猜你喜欢

转载自www.cnblogs.com/ywhyme/p/10657345.html