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是:
上面的testsuite的一种变体:
suite = unittest.TestSuite()
loader=unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestAdd))
suite.addTest(loader.loadTestsFromTestCase(TestAdd2))
得到的unittest是:
]> 通过上面的代码分析,测试框架核心功能是如何实现的都有了答案。包括testcase和testsuite的实现,测试用例的加载,执行测试用例时的信息收集和测试报告的生成。另外,如何让每个测试函数都执行setUp()和tearDown(),如何实现任何一个testcase的运行异常都不会影响后续testcase的执行,也都有了答案。