unittest case失败重跑机制

 转   https://blog.csdn.net/weixin_43106813/article/details/87279766

 

使用unittest+appium+python搭建app UI自动化测试框架。由于appium自身的不稳定性,case会在预期结果与实际结果一致的情况下执行失败,可以通过重试机制保持case的稳定性。查阅资料后发现,python的unittest自身无失败重试机制,现通过修改源码的方式实现失败case自动重跑
实现结果
通过修改源码,可以实现case执行失败后立刻重跑,且执行setUp/setUpClass,tearDown/tearDownClass,且第一次运行失败不记录结果,记录重跑后Case的执行结果。
定位case源码
通过debug定位case源码,发现case.py文件中的run()方法作用是运行测试用例,且将测试结果收集到TestResult中

 def run(self, result=None):      
        orig_result = result
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

        result.startTest(self)

        testMethod = getattr(self, self._testMethodName)
        if (getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)):
            # If the class or method was skipped.
            try:
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                self._addSkip(result, self, skip_why)
            finally:
                result.stopTest(self)
            return
        expecting_failure = getattr(testMethod,
                                    "__unittest_expecting_failure__", False)
        outcome = _Outcome(result)
        try:
            self._outcome = outcome
            #下面部分的代码逻辑是:执行setup部分,如果没有异常执行testMethod(即我们写的以test开头的TestCase类的方法),然后执行tearDown部分代码(不管testMethod是否通过)
            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()
            #下面部分的代码逻辑是:将case执行成功或是失败结果计入result中
            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

通过查看源码可以发现,我们可以在case执行失败后加一个循环来再次执行setUp、testMethod及tearDown,由于我们现有case采用setupClass进行整个case集执行前的初始化,直接循环并不满足需求,继续查看源码发现可以在记录执行结果部分加一个else判断:

if outcome.success:
                if expecting_failure:
                    if outcome.expectedFailure:
                        self._addExpectedFailure(result, outcome.expectedFailure)
                    else:
                        self._addUnexpectedSuccess(result)
                else:
                    result.addSuccess(self)
            return result

修改后:

if outcome.success:
                if expecting_failure:
                    if outcome.expectedFailure:
                        self._addExpectedFailure(result, outcome.expectedFailure)
                    else:
                        self._addUnexpectedSuccess(result)
                else:
                    result.addSuccess(self)
 else:
             logging.info("failed retry")
             #outcome.success置为true重新运行case
             outcome.success = True
            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

执行case后发现,case失败重跑后会多记录一遍执行结果,注释掉case第一次执行失败向result中记录error的代码,且case重跑之前清空error即可解决

self.doCleanups()
for test, reason in outcome.skipped:
    self._addSkip(result, test, reason)
#注释掉第一次运行case失败向result记录结果的步骤    
#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)
 else:
             logging.info("failed retry")
             #outcome.success置为true重新运行case
             outcome.success = True
             #outcome.errors重跑之前清空error记录
             outcome.errors = []
            with outcome.testPartExecutor(self):
                self.setUpClass()
            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

问题初步得到解决,用例执行失败后不记录执行结果,立即执行初始化setUpClass重跑,记录重跑后的执行结果,如有更灵活的方法请与我交流沟通~

posted @ 2020-08-31 22:00  测试艺术家  阅读(641)  评论(0编辑  收藏  举报