zoukankan      html  css  js  c++  java
  • 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重跑,记录重跑后的执行结果,如有更灵活的方法请与我交流沟通~

  • 相关阅读:
    大厂Redis高并发场景设计,面试问的都在这!
    POJ1006——中国剩余定理
    HDU3501——欧拉函数裸题
    堆-动态的排序(洛谷1801-黑匣子)
    图中欧拉回路数量
    ip地址
    网络通信概述
    网络通信概述
    软件安装与卸载
    软件安装与卸载
  • 原文地址:https://www.cnblogs.com/zhenyu1/p/13592217.html
Copyright © 2011-2022 走看看