python测试框架unittest源代码分析

Python3.6.3 unittest源代码分析

 

程序入口:

main.py中定义类TestProgram,它实现了运行测试的命令行程序。它不支持在代码中定义的testsuite。

 

测试加载:

loader.TestLoader将test加载到testsuite中。其中定义了discover(),用来支持用例的自动发现。因为unittest代码中有defaultTestLoader = TestLoader(),所以测试代码中可以直接使用loader.TestLoader的实例unittest.defaultTestLoader。

loadTestsFromModule()中实现了从一个文件中获取所有test。关键代码段如下:

for name in dir(module):

obj = getattr(module, name)

    if isinstance(obj, type) and issubclass(obj, case.TestCase):

        tests.append(self.loadTestsFromTestCase(obj))

这里obj为类时,isinstance(test, type)才为True,才会计算issubclass(obj, case.TestCase),这可以防止在obj不是类时issubclass()抛出异常。

loadTestsFromTestCase(self, testCaseClass)实现从一个TestCase中获取所有test,关键代码如下:

testCaseNames = self.getTestCaseNames(testCaseClass)

这里其实获取了testCaseClass中所有test函数的名字。

loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))

这里self.suiteClass是suite.TestSuite。

每个test函数,最终被实例化成一个类TestCase的对象testcase。这个testcase包括test函数,并且和对应的setUp()和tearDown()联系起来。

而在测试代码中,每个测试用例是一个类,其中有若干个test。

 

TestSuite的实现:

suite.py定义了类TestSuite。TestSuite是BaseTestSuite的子类。最重要的属性是_tests,它是一个列表。列表的元素可以是testsuite,也可以是testcase。

BaseTestSuite中addTest()中,先确认test是一个method,然后再添加到属性_tests中。如何确认test是一个method?首先callable(test)返回True,说明test是callable的,然后not(isinstance(test, type) and issubclass(test,(case.TestCase, TestSuite)))为True,说明不是未初始化的TestCase或者TestSuite,这时就认为test是一个method。

 

TestCase的实现:

case.py中定义了类TestCase。最重要的属性是_testMethodName。通过testMethod = getattr(self, self._testMethodName)获得要执行的测试函数。类TestCase的方法setUp()和tearDown()都是空的,是钩子方法。具体的测试用例类继承TestCase,重写setUp()和tearDown()。类TestCase中还实现了各种assert方法。

 

Test的执行:

case.TestCase.run()中,在try语句中依次执行self.setUp()、testMethod()和self.tearDown()。出现异常也不会影响后续测试用例执行。

 

运行时的信息收集:

result.TestResult的实例的收集信息。包括运行的测试总数,其中失败和出现错误的测试数目。另外通过一个列表来收集异常信息,列表的元素是 (test, exceptioninfo)。

 

测试报告的生成:

runner.TextTestResult是result.TestResult的子类。它将result.TestResult收集到的信息,以文本形式展现出来。它被runner. TextTestRunner使用。

runner. TextTestRunner以文本形式展示结果。

 

TestSuite的执行:

runner.TextTestRunner.run()中test(result)执行测试,后者最终调用case.TestCase.run()。

关于test(result):如果test的类型是TestCase,则调用case.TestCase.run(result)。如果test的类型是TestSuite,则调用suite.TestSuite.run(result),但是后者最后还是会调用case.TestCase.run(result)。

 

 

简单的测试案例执行流程分析:

import unittest

class TestAdd(unittest.TestCase):

    def setUp(self):

        print("set up")

    def test_add(self):

        result = 1+2

        self.assertEqual(result, 3)

    def tearDown(self):

        print("tear down")

unittest.main()

执行过程:

unittest.main()实际执行了main.py中的TestProgram()。

TestProgram()首先调用self.parseArgs(argv),后者调用self.createTests(),后者调用self.testLoader.loadTestsFromModule(self.module)。这里self.testLoader是loader.TestLoader的实例。最终完成用例的加载。

TestProgram()还调用了TestProgram.runTests(),后者中有testRunner.run(),缺省testRunner=runner.TextTestRunner。

runner.TextTestRunner.run()中有test(result)。后者调用suite.BaseTestSuite.__call__()。后者return self.run(*args, **kwds),即执行suite.TestSuite.run(result)。

对于suite中每一个子suite,执行test(result),最终再次执行suite.TestSuite.run(result)。

suite.TestSuite.run(result)中先做一些执行test之前的准备,然后再次执行test(result),后者调用Case.TestCase.__call__(),后者中return self.run(*args, **kwds),即执行case.TestCase.run(result)。

case.TestCase.run(result)执行self.setUp()、testMethod()和self.tearDown()。

 

上面的例子中testsuite是

]>]>

其中的testcase是test_add (__main__.TestAdd)

 

另一个testsuite举例:

suite = unittest.TestSuite()

loader=unittest.TestLoader()

suite.addTest(TestAdd('test_add1'))

suite.addTest(TestAdd('test_add2'))

suite.addTest(TestAdd2('test_add3'))

suite.addTest(TestAdd2('test_add4'))

得到的testsuite是:

, <__main__.TestAdd testMethod=test_add2>, <__main__.TestAdd2 testMethod=test_add3>, <__main__.TestAdd2 testMethod=test_add4>]>

 

上面的testsuite的一种变体:

suite = unittest.TestSuite()

loader=unittest.TestLoader()

suite.addTest(loader.loadTestsFromTestCase(TestAdd))

suite.addTest(loader.loadTestsFromTestCase(TestAdd2))

得到的unittest是:

, <__main__.TestAdd testMethod=test_add2>]>,

, <__main__.TestAdd2 testMethod=test_add4>]>

]>

 

通过上面的代码分析,测试框架核心功能是如何实现的都有了答案。包括testcase和testsuite的实现,测试用例的加载,执行测试用例时的信息收集和测试报告的生成。另外,如何让每个测试函数都执行setUp()和tearDown(),如何实现任何一个testcase的运行异常都不会影响后续testcase的执行,也都有了答案。

你可能感兴趣的:(Python)