疑问1、unittest框架中,是如何执行并运行以test开头的方法,而不运行不以test开头的方法?
疑问2、执行unittest.main() 后,具体发生了什么?
当我们写好测试用例,运行该测试用例,具体的运行原理是啥?通过分析源码,了解整个运行过程:从load测试用例到测试套件suite,到通过TextTestRunner去执行测试用例。
(注:我们将以test开头的方法,称为测试用例)
test_demo.py代码如下
import unittest
class ModuleTest1(unittest.TestCase):
def test1(self):
print 'test1'
def test2(self):
print 'test2'
class ModuleTest2(unittest.TestCase):
def test3(self):
print 'test3'
def test4(self):
print 'test4'
unittest.main()
python test_demo.py
当运行python test_demo.py ,则会运行unittest.main().
step1 : unittest.main()
通过unittest的源码,我们发现main.py中,
main = TestProgram
即unittest.main() = unittest.TestProgram() #即TestProgram类的实例对象
step2: TestProgram中__init__()
创建完TestProgram类的实例对象后,运行TestProgram类中的构造函数__init__()方法
TestProgram类的__init__()方法源码如下:
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None,
buffer=None):
if isinstance(module, basestring):
self.module = __import__(module)
for part in module.split('.')[1:]:
self.module = getattr(self.module, part)
else:
self.module = module
if argv is None:
argv = sys.argv
self.exit = exit
self.failfast = failfast
self.catchbreak = catchbreak
self.verbosity = verbosity
self.buffer = buffer
self.defaultTest = defaultTest
self.testRunner = testRunner
self.testLoader = testLoader
self.progName = os.path.basename(argv[0])
self.parseArgs(argv)
self.runTests()
通过上述的源码:核心点在self.parseArgs(argv)和self.runTests()。
self.parseArgs(argv)
self.runTests()
step 2.1. self.parseArgs(argv)
parseArgs(argv)源码如下:
def parseArgs(self, argv):
.....省略....
else:
self.testNames = (self.defaultTest,)
self.createTests()
except getopt.error, msg:
self.usageExit(msg)
通过如上源码,多余的不看,核心点在于self.createTests():
self.createTests()
self.createTests()源码如下:
def createTests(self):
if self.testNames is None:
self.test = self.testLoader.loadTestsFromModule(self.module)
else:
self.test = self.testLoader.loadTestsFromNames(self.testNames,
self.module)
从createTests()方法中,通过前面的代码,我们可以判定self.testNames是没有值的,故运行self.testLoader.loadTestsFromModule(self.module)。
self.testLoader.loadTestsFromModule(self.module)
通过TestProgram类中构造函数__init__()中,self.testLoader = testLoader
—>testLoader=loader.defaultTestLoader
---->load.py 文件中defaultTestLoader:
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None,
buffer=None):
....省略....
通过load.py文件中我们发现defaultTestLoader = TestLoader()
,所以
self.testLoader.loadTestsFromModule(self.module) ->等价于
loader.defaultTestLoader.loadTestsFromModule(self.module) ->等价于
loader.TestLoader().loadTestsFromModule(self.module)
故,运行Tload.py文件中TestLoader()类中的loadTestsFromModule()方法。
Step3 : TestLoader中loadTestsFromModule(self.module) 得到一个测试套件suite
loadTestsFromModule()源码如下:
def loadTestsFromModule(self, module, use_load_tests=True):
"""Return a suite of all test cases contained in the given module"""
tests = []
for name in dir(module):
obj = getattr(module, name)
if isinstance(obj, type) and issubclass(obj, case.TestCase):
tests.append(self.loadTestsFromTestCase(obj))
load_tests = getattr(module, 'load_tests', None)
tests = self.suiteClass(tests)
if use_load_tests and load_tests is not None:
try:
return load_tests(self, tests, None)
except Exception, e:
return _make_failed_load_tests(module.__name__, e,
self.suiteClass)
return tests
核心1 : 代码分析:
for name in dir(module): #for 循环遍历
#module即模块名,此时就是test_demo,dir(test_demo),返回test_demo模块下所有属性、方法、类,及相应的值
obj = getattr(module, name) #返回test_demo中name属性的值,obj
if isinstance(obj, type) and issubclass(obj, case.TestCase):#判别出obj是不是type ,是不是测试用例类TestCaseClass,并且继承unittest.TestCase类,目的:筛选出测试用例类,test_demo中ModuleTest1类和ModuleTest2类
tests.append(self.loadTestsFromTestCase(obj))
#如果obj是测试用例类TestCaseClass,并继承了承unittest.TestCase类,则调用self.loadTestsFromTestCase(obj),并将其返回的结果append到tests列表中
核心2 :
tests = self.suiteClass(tests)
核心1 : 代码具体调用、运行过程分析
Step4 : self.loadTestsFromTestCase(obj)
源码如下:
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
step1 :先调用
testCaseNames = self.getTestCaseNames(testCaseClass)
getTestCaseNames()方法,将测试用例类TestCaseClass中测试用例名(以test开头),以列表的形式返回,(即:test_demo.py 中,ModuleTest1 / or ModuleTest2中的[test1,test2] / or [test3,test4]).
调用testFnNames = filter(isTestMethod, dir(testCaseClass))
,用filter()方法,把testCaseClass中所有属性和方法,去匹配isTestMethod()方法,筛出所有测试用例名(以test开头)。
为啥一定是test开头法方法才是测试用例,才会被执行,而其他方法(不以test开头),不作为测试用例,不执行,由prefix=self.testMethodPrefix决定,通过源码,我们可以看到TestLoader()上定义了类属性,
testMethodPrefix = 'test'
def getTestCaseNames(self, testCaseClass):
"""Return a sorted sequence of method names found within testCaseClass
"""
def isTestMethod(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
return attrname.startswith(prefix) and \
hasattr(getattr(testCaseClass, attrname), '__call__')
testFnNames = filter(isTestMethod, dir(testCaseClass))
if self.sortTestMethodsUsing:
testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing))
return testFnNames
step2 :调用
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
self.suiteClass() -> 由suiteClass = suite.TestSuite ->得出suite.TestSuite(map(testCaseClass, testCaseNames))
将测试用例类(即,ModuleTest1,/or ModuleTest2),测试用例名list(即[test1,test2]] / or [ test3,test4]),通过map映射,并成为一个测试套件suite。
运行返回的结果如下:
如果是ModuleTest1,loaded_suite =
<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=test1>, <__main__.ModuleTest1 testMethod=test2>]>
或者ModuleTest2, loaded_suite =
<unittest.suite.TestSuite tests=[<__main__.ModuleTest2 testMethod=test3>, <__main__.ModuleTest1 testMethod=test3>]>
总结:self.loadTestsFromTestCase(obj) 返回的是由测试用例类与测试用例映射后,并形成一个测试套件suite。
回到loadTestsFromModule()中,将测试套件append到tests列表:
tests.append(self.loadTestsFromTestCase(obj))
因为前面有for循环,故得出的结果,将每一个测试用例类与它下面的测试用例映射后,创建测试套件suite,当成tests list中的元素,最终tests表里有好多个元素,每个元素都是一个测试套件suite,每个suite对应一个测试用例类与它的测试用例。
以test_demo.py 文件下的测试用例类与测试用例,为例:
tests = [
<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=test1>, <__main__.ModuleTest1 testMethod=test2>]>,
<unittest.suite.TestSuite tests=[<__main__.ModuleTest2 testMethod=test3>, <__main__.ModuleTest2 testMethod=test4>]>
]
核心代码2:
tests = self.suiteClass(tests)
将tests列表又变成一个测试用例suite。
以test_demo.py 文件下的测试用例类与测试用例,为例:
tests =
<unittest.suite.TestSuite tests=
[
<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=testhah1>, <__main__.ModuleTest1 testMethod=testtwo2>]>,
<unittest.suite.TestSuite tests=[<__main__.ModuleTest2 testMethod=testhah2>, <__main__.ModuleTest2 testMethod=testtwo2>]>
]
>
self.runTests() 主要实现如何 执行测试用例
源码如下:
def runTests(self):
if self.catchbreak:
installHandler()
if self.testRunner is None:
self.testRunner = runner.TextTestRunner
if isinstance(self.testRunner, (type, types.ClassType)):
try:
testRunner = self.testRunner(verbosity=self.verbosity,
failfast=self.failfast,
buffer=self.buffer)
except TypeError:
# didn't accept the verbosity, buffer or failfast arguments
testRunner = self.testRunner()
else:
# it is assumed to be a TestRunner instance
testRunner = self.testRunner
self.result = testRunner.run(self.test)
if self.exit:
sys.exit(not self.result.wasSuccessful())
由上可得self.result = testRunner.run(self.test)
, self.test = loadTestsFromModule()中返回值 tests, 调用了TextTestRunner中的run()方法:
def run(self, test):
"Run the given test case or test suite."
result = self._makeResult()
registerResult(result)
result.failfast = self.failfast
result.buffer = self.buffer
startTime = time.time()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
try:
test(result) #test是个TestSuite的实例对象suite,test(),会调用TestSuite类中的__call__()方法
.....其他代码省略....
TestSuite类中的__call__()方法,调用TestSuite中的run()方法:
def __call__(self, *args, **kwds): #调用 TestSuite中的run()方法
return self.run(*args, **kwds)
def run(self, result, debug=False):
.....代码省略...
for test in self: #通过一个for循环依次调用TestCase的run方法去执行具体的测试用例(这里要注意的是,如果test还是TestSuite类型,会继续递归调用)。
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) #调用TestCase中—__call__(),即调用TestCase中run()函数
else:
test.debug()
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
result._testRunEntered = False
return result
通过一个for循环,依次调用TestCase的run方法去执行具体的测试用例(这里要注意的是,如果test还是TestSuite类型,会继续递归调用)。
如何理解递归调用?
for test in self:
#type(test) 测试用例test***对应的类
#self :是个测试suite,遍历这个suite,这个suite里面可能还有suite,会继续递归调用。
#<举例:
#第一次遍历
# self:unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=test1>, <__main__.ModuleTest1 testMethod=test2>]>, <unittest.suite.TestSuite tests=[<__main__.ModuleTest2 testMethod=test3>, <__main__.ModuleTest2 testMethod=test4>]>]>
#test:<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=test1>, <__main__.ModuleTest1 testMethod=test2>]>
#因为test还是个suite,则此时,递归,self=<unittest.suite.TestSuite tests=[<__main__.ModuleTest1 testMethod=test1>, <__main__.ModuleTest1 testMethod=test4>]>
#test=test1 (__main__.ModuleTest1),运行之后的代码,并运行test用例test1,得出结果
#下一次取值:test= test2 (__main__.ModuleTest1)
#再一次:<unittest.suite.TestSuite tests=[<__main__.ModuleTest2 testMethod=test3>, <__main__.ModuleTest2 testMethod=test4>]>
# .....
再运行
test(result) #调用TestCase中—__call__(),即调用TestCase中run()函数
TestCase中run()函数
源码如下:
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()
self._resultForDoCleanups = result
result.startTest(self)
testMethod = getattr(self, self._testMethodName) #获取测试用例
....代码省略...
..........
testMethod() #执行测试用例
....................