TestSuite
测试套件类,如何理解测试套件这个概念呢,从它的类定义来看,可以理解为:多个独立的测试用例(test case)或者多个独立的测试套件(test suite,可以理解为子套件)可以构成一个测试套件,那么我们写好了一个用例之后,如果去构建一个测试套件呢。下面介绍几种构建测试套件的方法:
1. 通过unittest.TestSuite()类直接构建,或者通过TestSuite实例的addTests、addTest方法构建
2. 通过unittest.TestLoader类的discover、loadTestsFromTestCase、loadTestsFromModule、loadTestsFromName、loadTestsFromNames这五个方法去构建
3. 通过unittest.makeSuite()、unittest.findTestCases()这两个方法去构建
下面我们分别对以上几种方法分别举一个例子:我们先写好一个TestCase:
#使用unittest.TestSuite()类直接构建,或者通过TestSuite实例的addTests、addTest方法构建
import unittest
class UserCase(unittest.TestCase):
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
if __name__ == '__main__':
suite = unittest.TestSuite(map(UserCase,['testAddUser','testDelUser']))
suite2 = unittest.TestSuite()
suite2.addTests(map(UserCase,['testAddUser','testDelUser']))
suite3 = unittest.TestSuite()
suite3.addTest(UserCase('testAddUser'))
suite3.addTest(UserCase('testDelUser'))
#通过unittest.TestLoader类的discover、loadTestsFromTestCase、loadTestsFromModule、loadTestsFromName、loadTestsFromNames这五个方法去构建
import unittest
class UserCase(unittest.TestCase):
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
if __name__ == '__main__':
module = __import__(__name__)
suite = unittest.TestLoader().discover('.','unittest_user.py') #unittest_user.py
suite2 = unittest.TestLoader().loadTestsFromTestCase(UserCase)
suite3 = unittest.TestLoader().loadTestsFromModule(module)
#loadTestsFromName、loadTestsFromNames暂时不举例了,参数类型较多,不便举例,可以自行阅读其代码
#通过unittest.makeSuite()、unittest.findTestCases()这两个方法去构建
import unittest
class UserCase(unittest.TestCase):
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
if __name__ == '__main__':
module = __import__(__name__)
suite = unittest.makeSuite(UserCase,prefix='test')
suite2 = unittest.findTestCases(module,prefix='test')
上面介绍了创建TestSuite的几种方法,其实这几种方法最终都脱离不了通过TestSuite去创建测试集,不信? 那我们就拿TestLoader()的loadTestsFromTestCase来验证一下是不是这样子的。
我们先看loadTestsFromTestCase的方法实现:
class TestLoader(object):
"""
This class is responsible for loading tests according to various criteria
and returning them wrapped in a TestSuite
"""
testMethodPrefix = 'test'
sortTestMethodsUsing = cmp
suiteClass = suite.TestSuite
_top_level_dir = None
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all test cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
raise TypeError("Test cases should not be derived from TestSuite." \
" Maybe you meant to derive from TestCase?")
testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest']
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
return loaded_suite
我们注意到类里面的几个变量:
testMethodPrefix = ‘test’
sortTestMethodsUsing = cmp
suiteClass = suite.TestSuite
第一句是用来过滤我们的测试方法,即默认的以test开头的方法
第二句是用来对我们的测试方法名排序的方法cmp
第三句是对suiteClass赋值,即TestSuite类
loadTestsFromTestCase的参数是一个TestCaseClass,就是我们编写的测试类的类名,
重要的一行代码:
testCaseNames = self.getTestCaseNames(testCaseClass)
这是干嘛的呢,读者们可以阅读一下getTestCaseNames的方法的实现,可以看出它是在获取TestCaseClass中所有以“test”开头的方法名,并且以字符串排序的方式排好序后并返回一个列表。然后还有一行重要的代码:
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
这就是我刚刚上面说的“所有的构建TestSuite方法都脱离不了通过TestSuite去创建测试集”的根源,loaded_suite最终的的值就是TestSuite的实例
有人可能会问,我们最终运行的是一个个的测试用例,而不是一整个测试集,既然我们是创建的测试集,那它是如何去运行我们的测试用例的呢。别着急,答案马上揭晓。
测试集TestSuite类中跟TestCase一样也有一个run方法,我们先看代码:
class TestSuite(BaseTestSuite):
def run(self, result, debug=False):
topLevel = False
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
for test in self:
if result.shouldStop:
break
if _isnotsuite(test):
self._tearDownPreviousClass(test, result)
self._handleModuleFixture(test, result)
self._handleClassSetUp(test, result)
result._previousTestClass = test.__class__
if (getattr(test.__class__, '_classSetupFailed', False) or
getattr(result, '_moduleSetUpFailed', False)):
continue
if not debug:
test(result)
else:
test.debug()
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
result._testRunEntered = False
return result
注意TestSuite是继承BaseTestSuite类的,并且重写了BaseTestSuite类run方法。并且参数是result,这跟TestCase类的run方法的参数一样,后面的debug参数我们先不关注。
前面三行代码我们也先忽略,我们来看中间部分的代码:
for test in self
第一句就是循环,迭代器的用法,为什么说是迭代器呢,因为BaseTestSuite类实现了iter魔术方法。我们可以看一下:
def __iter__(self):
return iter(self._tests)
这下知道为什么可以循环了吧,并且每一次迭代返回的是一个test,这个test是什么呢,就是我们创建测试套件时往里面添加的TestCase的实例或TestSuite的实例。
接着往下看:
if _isnotsuite(test):
self._tearDownPreviousClass(test, result)
self._handleModuleFixture(test, result)
self._handleClassSetUp(test, result)
result._previousTestClass = test.__class__
if (getattr(test.__class__, '_classSetupFailed', False) or
getattr(result, '_moduleSetUpFailed', False)):
continue
这段代码是干嘛的呢,我总结了一下,是处理setUpClass和TearDownClass部分的逻辑(里面的方法的源码较多,暂时就不一一解释了),如果setUpClass失败,则continue跳出本次循环继续执行下一个用例,包括==后面测试用例的执行和TearDownClass的执行==
(所以如果我们的用例里面有重写了setUpClass,并且失败了,是不会继续往下执行的)
继续往下读代码,如果setUpClass没有失败,则继续执行下面的代码:
if not debug:
test(result)
else:
test.debug()
上面我们说过这个test是我们实例化的一个TestCase类或TestSuite类,假如这个test就是TestCase类的实例吧。然后test(result)这句是干嘛的呢,还记得TestCase类里面的run方法吗,没错,这句话就是调用的TestCase类里面的run方法去执行用例的。这下明白了TestSuite是怎样去执行我们的TestCase了吧。
简单总结一下就是,测试套件(TestSuite)的执行是通过它的run方法,而它的run方法又会去调用每一个用例(TestCase)的run方法。这样就实现了用例的执行
不知道大家有没有注意到,TestCase和TestSuite的run方法中有一个比较明显的区别:
在TestCase中的run方法没有去处理setUpClass和TearDownClass部分的逻辑代码,而在TestSuite的run方法中才有处理setUpClass和TearDownClass部分的逻辑代码。所以这个地方是有一些区别的。在本篇文章的最后的 [补充部分] 我举例说明一下他们执行后的不同。
还有一小部分代码,已经无关紧要了,也是处理我们写的测试用例类的TearDownClass部分的:
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
result._testRunEntered = False
return result
好了,我们的测试套件TestSuite分析到这里就差不多结束了,写的有些烂,希望大家能有所收获吧。
下一篇的unittest之TextTestRunner类详解我会继续分析TextTestRunner类。期待中。。。
[补充部分]:
TestCase和TestSuite的run方法的不同点:
#unittest_prac.py
import sys
import unittest
class UserCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
print('setUpClass')
def setUp(self):
print("setup")
def testAddUser(self):
print("add a user")
def testDelUser(self):
print("delete a user")
def tearDown(self):
print('teardown')
@classmethod
def tearDownClass(cls):
print('tearDownClass')
if __name__ == '__main__':
#实例化一个TextTestResult类
result = unittest.TextTestResult(sys.stdout,'test result',1)
suite = unittest.TestSuite(map(UserCase,['testAddUser']))
suite.run(result)
testcase = UserCase('testAddUser')
testcase.run(result)
C:\software\PythonWorkspace>python unittest_prac.py
setUpClass
setup
add a user
teardown
.tearDownClass
setup
add a user
teardown
.
可以很明显的看到,TestSuite的run方法执行setUpClass和tearDownClass,但TestCase的run方法没有执行setUpClass和tearDownClass