python 单元测试中处理用例失败的情况

发表于:2019-7-29 13:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:ywhyme    来源:网络

  今天有一个需求, 在单元测试失败的时候打印一些日志, 我们管他叫 dosomething 吧 ,反正就是做一些操作
  查了下并没有查到相关的方法, 于是研究了一波unittest 的源码
  发现了这个东西
   try:
  self._outcome = outcome
  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
  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
  其中重点关注下testMethod() 这个正是我们执行的用例
  于是去看了下testPartExecutor 用例失败的处理是在这里进行处理的
   def testPartExecutor(self, test_case, isTest=False):
  old_success = self.success
  self.success = True
  try:
  yield
  except KeyboardInterrupt:
  raise
  except SkipTest as e:
  self.success = False
  self.skipped.append((test_case, str(e)))
  except _ShouldStop:
  pass
  except:
  exc_info = sys.exc_info()
  if self.expecting_failure:
  self.expectedFailure = exc_info
  else:
  self.success = False
  self.errors.append((test_case, exc_info))
  # explicitly break a reference cycle:
  # exc_info -> frame -> exc_info
  exc_info = None
  else:
  if self.result_supports_subtests and self.success:
  self.errors.append((test_case, None))
  finally:
  self.success = self.success and old_success
  奈何, 他只是在self.errors (其中self为我们测试类的一个实例)中加了点东西
  于是对self.errors 进行观察,发现及时用例是正常的,他依然由内容.
  这..............于是我想到他最终是怎么打出来失败的log的
  看到 上面代码中的 self._feedErrorsToResult(result, outcome.errors)
  于是找到了这个东西
   def _feedErrorsToResult(self, result, errors):
  for test, exc_info in errors:
  if isinstance(test, _SubTest):
  result.addSubTest(test.test_case, test, exc_info)
  elif exc_info is not None:
  if issubclass(exc_info[0], self.failureException):
  result.addFailure(test, exc_info)
  else:
  result.addError(test, exc_info)
  从上面的代码我们可以知道 如果 error的第二项是None那么就是一个执行成功的用例,经过实验并确认了这个事情
  现在知道了 unittest 是如何处理 失败用例的了
  于是便有了下面这种方法
   def tearDown(self):
  errors = self._outcome.errors
  for test, exc_info in errors:
  if exc_info:
  # dosomething
  pass
  上面这种方法尽量少的改变原来的逻辑, 想到一种新的方法解决问题
  既然unittest没有处理这个事情,那我们魔改之
  于是有了下面这种方法
  注意: 不建议魔改代码
   import sys
  import contextlib
  import unittest
  from unittest.case import SkipTest, _ShouldStop, _Outcome
  @contextlib.contextmanager
  def testPartExecutor(self, test_case, isTest=False):
  old_success = self.success
  self.success = True
  try:
  yield
  except Exception:
  try:
  # if error
  getattr(test_case, test_case._testMethodName).__func__._error = True
  raise
  except KeyboardInterrupt:
  raise
  except SkipTest as e:
  self.success = False
  self.skipped.append((test_case, str(e)))
  except _ShouldStop:
  pass
  except:
  exc_info = sys.exc_info()
  if self.expecting_failure:
  self.expectedFailure = exc_info
  else:
  self.success = False
  self.errors.append((test_case, exc_info))
  # explicitly break a reference cycle:
  # exc_info -> frame -> exc_info
  exc_info = None
  else:
  if self.result_supports_subtests and self.success:
  self.errors.append((test_case, None))
  finally:
  self.success = self.success and old_success
  _Outcome.testPartExecutor = testPartExecutor
  class MyTest(unittest.TestCase):
  def test_1(self):
  print("test_1")
  def test_2(self):
  print("test_2")
  raise ValueError
  def tearDown(self):
  if hasattr(getattr(self, self._testMethodName), "_error"):
  # dosomething
  pass
  # def tearDown(self):
  #     推荐这种方法
  #     errors = self._outcome.errors
  #     for test, exc_info in errors:
  #         if exc_info:
  #             # dosomething
  #             pass
  if __name__ == '__main__':
  unittest.main()
  这样我们就可以在用例执行失败后在tearDown的时候做一些操作

      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号