TestCase
测试用例类,我们做单元测试时编写的测试用例就是继承TestCase类来实现具体的测试用例 \
例如:
import unittest
class UserCase(unittest.TestCase):
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
if __name__ == '__main__':
unittest.main()
可以这样理解:每一个继承TestCase类的子类里面实现的具体的方法(以test开头的方法)都是一条用例 \
既然我们写了用例,那用例又是如何被执行的呢:这就是下面要讲的TestCase类里面run方法,先贴一下代码:
def run(self, result=None):
orig_result = result
#初始化result类,此类就是TestResult类或者其子类,用来记录用例的执行结果信息等
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
self._resultForDoCleanups = result
result.startTest(self)
testMethod = getattr(self, self._testMethodName) #获取当前TestCase类里面实现的某一个具体的方法(以test开头的方法)
#判断TestCase类是否有被skip装饰,或类的testMethod方法有没有被skip装饰,如果有skip装饰,则直接跳过这个类或者这个方法不执行(包括后面tearDown也不会被执行),
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, skip_why)
finally:
result.stopTest(self)
return
#下面部分的代码逻辑是:执行setup部分,如果没有异常执行testMethod(即我们写的以test开头的TestCase类的方法),然后执行tearDown部分代码(不管testMethod是否通过)
try:
success = False
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)
#此部分是用例执行完成之后的收尾工作
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
注意:run方法的第一个参数是result
下面详细说明一下run方法里面得逻辑:
orig_result = result
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
self._resultForDoCleanups = result
result.startTest(self)
这一部分代码是用来实例化一个TestResult类,如果run方法没有传入result参数,则用self.defaultTestResult方法返回一个TestResult类的实例,并且如果TestResult如果有startTestRun方法就执行startTestRun方法。后面两句中的第一句是将result赋值TestCase实例_resultForDoCleanups属性,这个会在后面再说,第二句是执行result的startTest方法(可以看一下TestResult的startTest方法)。
继续往下看代码:
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, skip_why)
finally:
result.stopTest(self)
return
第一句代码:testMethod = getattr(self, self._testMethodName),这句主要获取TestCase类里面的方法,而self._testMethodName就是我们编写测试类里面的的方法(以test开头的方法)。比如上面例子中的testAddUser方法\
后面部分代码是判断当前我们写的TestCase类是否有unittest_skip,或者我们写的TestCase类的方法(以test开头)是否有unittest_skip方法,如果有则获取类的unittest_skip_why属性或者方法的unittest_skip_why属性。并在result中添加skip的原因。之后执行result的stopTest方法,并直接返回不继续执行下去(==包括setUp和teardown都不会执行==)。那么我们如何去实现这skip的逻辑呢,举例如下:
1、TestCase带有__unittest_skip__属性:
@unittest.skip('class skip')
class UnitTestCase(unittest.TestCase):
def test01(self):
print('test01')
def test02(self):
print('test02')
def tearDown(self):
print('teardown')
if __name__ == '__main__':
unittest.main()
2、方法带有__unittest_skip__属性
@unittest.skip('class skip')
class UnitTestCase(unittest.TestCase):
@unittest.skip('skip')
def test01(self):
print('test01')
@unittest.skip('skip')
def test02(self):
print('test02')
def tearDown(self):
print('teardown')
if __name__ == '__main__':
unittest.main()
而unittest_skip、unittest_skip_why这两个属性是如何给类或方法带上的呢,那让我们来看一下skip方法。先贴代码:
def skip(reason):
"""
Unconditionally skip a test.
"""
def decorator(test_item):
if not isinstance(test_item, (type, types.ClassType)):
@functools.wraps(test_item)
def skip_wrapper(*args, **kwargs):
raise SkipTest(reason)
test_item = skip_wrapper
test_item.__unittest_skip__ = True
test_item.__unittest_skip_why__ = reason
return test_item
return decorator
从代码中可以看出skip方法会给test_item(也就是类名或者方法名)增加两个属性unittest_skip,unittest_skip_why并分别赋值为True和reason。
我们继续上面的run方法剩下部分的代码分析:如果没有skip装饰类或方法,则执行下面的逻辑:
try:
success = False
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)
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
我们分解开来分析,第一部分:
try:
success = False
self.setUp()
except SkipTest as e:
self._addSkip(result, str(e))
except KeyboardInterrupt:
raise
except:
result.addError(self, sys.exc_info())
在用例执行之前给是否成功的标识赋值为false,success=False \
接着执行setUp方法,如果是在setUp中skipTest异常抛出,则在result中添加异常信息。如何实现这个SkipTest异常呢,我们可以在setUp方法中增加一句代码:raise unittest.SkipTest(‘setup skip test’),如果是在setUp中遇到KeyboardInterrupt异常(Ctrl+c),则会直接抛出异常,如果是除了这两个异常之外的其他异常,则在result中添加error信息。
第二部分:
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)
如果是setup部分没有报错或异常,则执行我们的testMethod方法(即我们写的具体的用例方法,以test开头),接下来的代码都是处理异常,else部分是如果没有异常则把success标识置为true,表示用例执行通过,然后执行tearDown部分的代码,如果tearDown部分遇到异常了,success标识又置为false。
第三部分:
finally:
result.stopTest(self)
if orig_result is None:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
执行result的stopTest和stopTestRun方法(如果有)\
至此,run方法分析完成了。
上面我们介绍了run方法,那又是什么时候会调用run方法呢,再看TestCase的类里面方法,发现有一个call魔术方法:
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
会去调我们的run方法(如果不太明白call方法是如何使用的,请百度一下),这就让我们知道肯定有一个地方调用了类似这样一个方法(以上面的例子为例):TestCase(‘testAddUser’)(result),这就是我的下一篇分析unittest之TestSuite类说明要讲的内容了
我们根据上面的分析可以来举一个列子来试验一下:
import unittest
class UserCase(unittest.TestCase):
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
if __name__ == '__main__':
result = unittest.TextTestResult(sys.stdout,'test result',1) #初始化TextTestResult类实例
testcase = UserCase('testAddUser') #初始化UserCase类实例
testcase(result) #跟testcase.run(result)的结果是一样的,我们只需要传入一个result对象即可
运行结果如下:
E:\PythonWorkSpace>python test.py
add a user
.
E:\PythonWorkSpace>
可以运行成功,说明跟我们的分析一直,我们可以直接通过TestCase的run方法运行我们的用例,或者用过它的call魔术方法也是可以的,即上面例子中的testcase(result)就是通过此魔术方法去调用的run方法。