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