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