【Python測試】unittest原始碼解析一----測試用例是如何被執行的
在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