1. 知识点
- 使用装饰器。
- 熟悉一遍unittest.TestCase源码(这里主要用到_Outcome类)。
2. TesetCase
官方介绍:
TestCase 类的实例代表 unittest Universe 中的逻辑测试单元。此类旨在用作基类,具体测试由具体子类实现。此类实现了测试运行程序所需的接口以允许其驱动测试,以及测试代码可用于检查和报告各种故障的方法。
通俗说就是:TestCase 类是一个基类,测试类需要继承unittest.TestCase 类,测试类来实现相关测试用例。unittest.TestCase 类实现了一些接口(Hook method),例如setUp 、tearDown 、setUpClass 、tearDownClass ,以及检查和报告各种故障的方法。
3. run()方法
run()方法是执行测试用例的地方,也是TesetCase类的核心方法。
源码:
def run(self, result=None):
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
stopTestRun = getattr(result, 'stopTestRun', None)
if startTestRun is not None:
startTestRun()
else:
stopTestRun = None
result.startTest(self)
try:
testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, self, skip_why)
return result
expecting_failure = (
getattr(self, "__unittest_expecting_failure__", False) or
getattr(testMethod, "__unittest_expecting_failure__", False)
)
outcome = _Outcome(result)
try:
self._outcome = outcome
with outcome.testPartExecutor(self):
self._callSetUp()
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self, isTest=True):
self._callTestMethod(testMethod)
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self._callTearDown()
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:
outcome.errors.clear()
outcome.expectedFailure = None
self._outcome = None
finally:
result.stopTest(self)
if stopTestRun is not None:
stopTestRun()
分为几个步骤来看:
第一部分: (调用self.defaultTestResult(),创建了一result,用于记录测试结果。)
def run(self, result=None):
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
stopTestRun = getattr(result, 'stopTestRun', None)
if startTestRun is not None:
startTestRun()
else:
stopTestRun = None
result.startTest(self)
首先判断是否传入了result,如果用unittest.main() 执行的测试用例,默认result=None 。使用self.defaultTestResult() 方法创建了一个TestResult类。然后查找TestResult类中有没有的startTestRun 、stopTestRun 方法,如果有这个方法则调用,但是startTestRun 与stopTestRun 默认没有实现任何功能,需要用户自定义。最后一句是执行startTest方法。主要初始化一些用例统计数字、缓存等。
第二部分: (找是否有需要跳过和预期失败的测试用例)。
try:
testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', ''))
self._addSkip(result, self, skip_why)
return result
expecting_failure = (
getattr(self, "__unittest_expecting_failure__", False) or
getattr(testMethod, "__unittest_expecting_failure__", False)
)
获取用例的名称,判断TestCase类或者用例是否有标记跳过的unittese_skip ,通过self._addSkip() 方法向result中添加skip的原因,然后 return result,expecting_failure :获取测试类或用例中带有标记预期失败的用例。
第三部分: 开始执行测试用例。创建_Outcome 类的实例对象,传入result,并赋值给TestCase 类的self._outcome 属性,用户记录信息。
outcome = _Outcome(result)
try:
self._outcome = outcome
with outcome.testPartExecutor(self):
self._callSetUp() // 执行用例的setUp() 方法
if outcome.success:
outcome.expecting_failure = expecting_failure // 记录将预期失败的用例
with outcome.testPartExecutor(self, isTest=True):
self._callTestMethod(testMethod) // 执行用例
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self._callTearDown() // 执行用例的tearDown()方法
self.doCleanups()
- outcome = _Outcome(result):存储TestResult的执行结果。
- 然后开启
testPartExecutor 上下文处理器开始执行用例。 self._callSetUp() :执行用例中的setUp()方法。self._callTestMethod(testMethod) ::执行用例。self._callTearDown() :执行用例的tearDown()方法。
第四部分: 执行完用例之后,处理成功、跳过、失败、预期失败的用例添加到result中。
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
for循环将outcome是否有跳过的用例,如果有则添加到result中。if 用来判断第三部分的执行测试用例或者tearDown是否成功,如果运行没有报错或者出错,将预期失败的用例和成功的用例结果都添加到result中。
_feedErrorsToResult 处理错误消息的方法。 将错误失败的用例信息添加到result中。
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)
最后一步: 做清除操作。
finally:
outcome.errors.clear()
outcome.expectedFailure = None
self._outcome = None
finally:
result.stopTest(self)
if stopTestRun is not None:
stopTestRun()
3. 注意
每一个case都是TestCase类的一个实例化对象(重要)。 每一个case都是TestCase类的一个实例化对象(重要)。 每一个case都是TestCase类的一个实例化对象(重要)。
例如: 用例test_1 是unittest.TestCase 类的一个实例对象。 用例test_2 也是unittest.TestCase 类的一个实例对象。
test_1实例对象 != test_2实例对象
class TestDemo(unittest.TestCase):
def test_1(self):
self.a = 1
print('第一个测试用例')
def test_2(self):
print('第二个测试用例')
// print(self.a) 不能访问a
if __name__ == '__main__':
unittest.main()
4. 解决方案
通过上面简单的分析可以知道,_Outcome 类记录测试用例成功、失败、跳过的信息,并且通过TeseCase类中的self._outcome 接收。并且源码中也是通过传入outcome.errors 处理失败的用例。
_Outcome类部分源码 : outcome = _Outcome(result),这个地方已经传过值了。
outcome = _Outcome(result)
class _Outcome(object):
def __init__(self, result=None):
self.expecting_failure = False
self.result = result
self.result_supports_subtests = hasattr(result, "addSubTest")
self.success = True
self.skipped = []
self.expectedFailure = None
self.errors = []
所以可以使用self._outcome.result.failures 来获取失败的用例。
if self._outcome.result.failures or self._outcome.result.errors:
// self._outcome.result.failures[0][0]._testMethodName 用例名称
raise unittest.SkipTest(func.__name__,self._outcome.result.failures[0][0]._testMethodName)
5. 最终代码
使用装饰器
import unittest
from functools import wraps
def CustomSkip(func):
@wraps(func)
def wrappend(self):
if self._outcome.result.failures or self._outcome.result.errors:
raise unittest.SkipTest(func.__name__,self._outcome.result.failures[0][0]._testMethodName)
func(self)
return wrappend
class TestDemo(unittest.TestCase):
def test_1(self):
print('第一个测试用例')
@CustomSkip
def test_a(self):
self.assertEqual(1,3)
@CustomSkip
def test_b(self):
print('测试用例bbbbb')
@CustomSkip
def test_c(self):
print('测试用例ccccc')
if __name__ == '__main__':
unittest.main()
其他学习链接:
可以参考大神的: 1.深入解读Python的unittest并拓展HTMLTestRunner 2.unittest跳过测试用例进阶版-----上一个测试用例失败后跳过下一个测试用例
有不对的地方请大家帮忙看看,一起学习学习!
|