在Python的单元测试中,有各种不同方式来执行用户的测试用例,在接下来的篇幅中,我们会详细叙述每种方式的具体执行流程。
先来看下unittest中的__init__.py中提供的一个测试用例案例:
import unittest
class IntegerArithmeticTestCase(unittest.TestCase):
def testAdd(self): ## test method names begin 'test*'
self.assertEqual((1 + 2), 3)
self.assertEqual(0 + 1, 1)
def testMultiply(self):
self.assertEqual((0 * 10), 0)
self.assertEqual((5 * 8), 40)
if __name__ == '__main__':
unittest.main()
可以把上面的用例放到一个arithmetic.py中,然后执行命令python arithmetic.py,测试用例会被依次运行。
那么,unittest是怎么提取相应的测试用例进行的运行的呢?
先来看一下unittest下面有哪些文件,分别是:__init__.py,__main__.py,case.py,loader.py,main.py,result.py,runner.py,signals.py,suite.py,utils.py。
这里先不描述这些文件都有什么用,因为在执行流程的叙述中自然会说明白它们的作用。
1.
unittest.main()开始了整个单元测试,这里实际上是调用了main.py中的TestProgram类的构造函数,因为在该文件中有这样一条语句:main = TestProgram。
def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None,
buffer=None):
print str(self.__class__) + " " + str(sys._getframe().f_lineno)
if isinstance(module, basestring):
print str(self.__class__) + " " + str(sys._getframe().f_lineno)
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()
上面是TestProgram的构造函数,这里的module默认是__main__,然后会去动态加载当前的module,即self.module = __import__(module)。
这里是为了执行测试用例所在的module。再来看下另外个参数testLoader=loader.defaultTestLoader,在loader.py中设置了defaultTestLoader = TestLoader()。
构造函数中进行了一系列的初始化操作,最后分别执行self.parseArgs(argv)和runTests()去提取和执行测试用例。
self.parseArgs(argv)会执行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)
这里testNames是None,所以会执行:
self.testLoader.loadTestsFromModule(self.module)
进入loader.py的loadTestsFromModule:
def loadTestsFromModule(self, module, use_load_tests=True):
"""Return a suite of all tests cases contained in the given module"""
tests = []
print module
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
这里是把self.loadTestsFromTestCase(obj)得到suite追加到tests列表中,然后把tests也转化为suite,并且返回。(这里面包含了所有的TestCase)
2.
下面来看看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())
在runTests()中会执行testRunner.run(self.test),也就是说会去执行TextTestRunner中的run方法,并把原先获取的TestSuite对想传入。
runner.py
try:
test(result)
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
这里会回调TestSuite中的run方法。
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
def run(self, result, debug=False):
print str(self.__class__) + " " + str(sys._getframe().f_lineno) + "\n"
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
在run中,通过一个for循环依次调用TestCase的run方法去执行具体的测试用例(这里要注意的是,如果test还是TestSuite类型,会继续递归调用)。
case.py
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
进入TestCase的run方法:
获取测试方法
testMethod = getattr(self, self._testMethodName)
执行获取到的测试方法
else:
try:
testMethod()
except KeyboardInterrupt:
raise