zoukankan      html  css  js  c++  java
  • python unittest case运行失败重试

    unittest原理:https://www.jianshu.com/p/c3fd61ac09e9

    因为使用unittest进行管理case的运行。有时case因为偶然因素,会随机的失败。通过重试机制能够补充保持case的稳定性。查阅资料后发现,python的unittest自身无失败重试机制,可以通过以下手段达到目的:1.修改unittest源码,使test case重新运行若干次 2. 对case结果进行处理,单独调度运行失败的case。此篇我们来了解下如何通过修改源码进行失败重试。使用的python版本为2.7。

    * 百度上搜索失败重试,找到这个文章。http://blog.csdn.net/hqzxsc2006/article/details/50349664。我尝试了此方法,并对其中部分逻辑进行了修改。后来发现我需要的运行场景中此方法无法满足要求,随放弃了该方法。

    1. 调用unittest运行case

    def TestSuite(filedirname):
        loader = unittest.TestLoader()
        dir_case = loader.discover(start_dir=config.case_dir, pattern=filedirname, top_level_dir=None)
        return dir_case
    
    
    if __name__ == '__main__':
        filedirname = 'Test*'  # 需要执行的case
        unittest.TextTestRunner(verbosity=2).run(TestSuite(filedirname))
    

    2. 定位运行case的源码1

      以上为调用unittest的方法。通过loader.discover扫描指定dir中所有以Test打头的文件,加载所有的case。然后通过unittest.TextTestRunner(verbosity=2).run()方法来运行所有的case。进入 TextTestResult类查看run方法,理解代码后发现:先初始化运行结果result,然后初始化startTestRun,运行test(result)后,处理result里记录的结果。通过加入打印等方式发现,此处的test(result),将加载的所有case都运行了。所以无法在此处做失败重试。

    3. 定位运行case的源码2

      通过函数定义可看出:TextTestResult.run(self, test)中的test即为:TestSuite(filedirname)函数的返回值。查看TestLoader.discover()函数,发现返回类型为:TestSuite。此类的run方法,即为搜索到的文章所修改的代码处。源码不支持中文,注释中有中文也不行,否则运行case将报错。以下为了写文章方便,使用中文注释。

    class TestSuite(BaseTestSuite):
        def run(self, result, debug=False):
            failcount = 1
            class_num = 1
            topLevel = False
            if getattr(result, '_testRunEntered', False) is False:
                result._testRunEntered = topLevel = True
    
            for test in self:
                case_num = 1
                if result.shouldStop:
                    break
                
                success_flag = True
                while success_flag:  
                    if _isnotsuite(test):
                        self._tearDownPreviousClass(test, result)
                        self._handleModuleFixture(test, result)
                        self._handleClassSetUp(test, result)
                        result._previousTestClass = test.__class__
                        if (getattr(test.__class__, '_classSetupFailed', False) or
                            getattr(result, '_moduleSetUpFailed', False)):
                            if class_num > failcount:
                                success_flag = False
                            else:
                                time.sleep(5)
                                result._previousTestClass = None
                                print 'Class %s retry time(s) %s'%(test.__class__,class_num)
                                class_num += 1
                            continue
              f,e = map(len, (result.failures, result.errors))  #查看result类得知失败和错误的保存在此
              cntBefore = f + e
                    if not debug:
                        test(result)
                    else:
                        test.debug()
                    
             f,e = map(len, (result.failures, result.errors))
             cntAfter = f + e 
                    if cntAfter > cntBefore:  
                        if case_num > failcount:
                            success_flag = False
                        else:  
                            print 'Test % retry time(s): %s'%(test,case_num)
                            case_num += 1
                    else:
                        success_flag = False
                        
            if topLevel:
                self._tearDownPreviousClass(None, result)
                self._handleModuleTearDown(result)
                result._testRunEntered = False
            return result

      实验中,通过以下方法调用,使用ok:

    def suit():
        s = unittest.TestSuite()
        s.addTest(Test_map("test_xxx1"))
        s.addTest(Test_map("test_xxx2"))
        unittest.TextTestRunner().run(s)
    
    if __name__ == '__main__':
        suit()
    

      当我以为问题解决时,发现通过1中的unittest.TextTestRunner(verbosity=2).run(TestSuite(filedirname))执行时,即行不通。当case失败时,就一直重试,进入死循环。debug时,发现当return result后,代码再一次进入了test(result),并且case_num=1,success_flag=True。所以进入循环,无法出来了。为何return后又进入test(result)了呢,我没理解出来....

     3. 定位运行case源代码3

      继续寻找合适的位置修改代码。debug时发现,TestSuite.run中的test(result)方法,实际上运行的是case.py中的TestCase.run方法。在此处修改也应可以实现重试。run中的其他代码保持不变。

                retryCnt = 0
                retryMax = 3
    
                success = False
                while (not success) and (retryCnt < retryMax):  #此处加入一个循环
                    print "RetryCnt:", retryCnt
                    try:
                        self.setUp()
                    except SkipTest as e:
                        self._addSkip(result, str(e))
                    except KeyboardInterrupt:
                        raise
                    except:
                        result.addError(self, sys.exc_info())
                    else:
                        try:
                            testMethod()
                        except KeyboardInterrupt:
                            raise
                        except self.failureException:
                            result.addFailure(self, sys.exc_info())
                        except _ExpectedFailure as e:
                            addExpectedFailure = getattr(result, 'addExpectedFailure', None)
                            if addExpectedFailure is not None:
                                addExpectedFailure(self, e.exc_info)
                            else:
                                warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
                                              RuntimeWarning)
                                result.addSuccess(self)
                        except _UnexpectedSuccess:
                            addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
                            if addUnexpectedSuccess is not None:
                                addUnexpectedSuccess(self)
                            else:
                                warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
                                              RuntimeWarning)
                                result.addFailure(self, sys.exc_info())
                        except SkipTest as e:
                            self._addSkip(result, str(e))
                        except:
                            result.addError(self, sys.exc_info())
                        else:
                            success = True
    
                        try:
                            self.tearDown()
                        except KeyboardInterrupt:
                            raise
                        except:
                            result.addError(self, sys.exc_info())
                            success = False
    
                    cleanUpSuccess = self.doCleanups()
                    success = success and cleanUpSuccess
                    if success:
                        result.addSuccess(self)
                    else:  #此次为新加入的代码
                        retryCnt += 1
                        print "----run case failed---"        
    

      可以看到,基本上只是加入了一个循环和else分支。运行后,得到了与预期基本一致的结果。case A若失败,会重新运行,直到成功。每个case最多运行retryMax次。结果中记录每次运行的结果,如case A运行失败1次,成功1次。

  • 相关阅读:
    CC_UNUSED_PARAM 宏含义的解释
    JAVA 字符串编码总结
    cocos-html5 Json 灵活 遍历方式 不同方式的缺陷,优点总结
    selenium--获取HTML源码断言和URL地址
    django -- 路由
    selenium--表格和复选框的定位
    selenium--加载浏览器配置
    django -- 视图
    django -- 母版继承
    selenium--浏览器窗口截图
  • 原文地址:https://www.cnblogs.com/sunada2005/p/6075081.html
Copyright © 2011-2022 走看看