1. 程式人生 > >【Python測試】unittest原始碼解析一----測試用例是如何被執行的

【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