1.TestSuite
TestSuite类用来对测试用例进行组合分类,通常称为测试套件。我们可以将不同位置的测试用例集合到一个测试套件内,并利用其内部实现的run方法来执行。
为了直观的理解TestSuite的用法,下面先定义了两个测试类。每个测试类里分别定义了几个方法。
import unittest class TestDemo01(unittest.TestCase): def test01(self): print('This is test01.') def test02(self): print('This is test02.') def test03(self): print('This is test03.') class TestDemo02(unittest.TestCase): def test04(self): print('This is test04.') def test05(self): print('This is test05.') |
在执行的代码段中,首先加入TestDemo01中的test02方法来构建出测试套件suite01,然后重新构建一个测试套件suite02,包含suite01与TestDemo02中的test04方法共同构成的一个元组。最后使用run方法将执行的结果保存到r变量中。这里需要特别说明,tests的参数类型必须是可迭代的,例如本例中的列表和元组。
if __name__ == '__main__': suite01 = unittest.TestSuite(tests=[TestDemo01('test02')]) suite02 = unittest.TestSuite(tests=(suite01,TestDemo02('test04'))) r = unittest.TestResult() suite02.run(result=r) print(r.__dict__) |
本例中调用的是suite02中的run方法,那么运行的是suite02套件内的测试用例,依次为test02和test04。
This is test02. This is test04. {'failfast': False, 'failures': [], 'errors': [], 'testsRun': 2, 'skipped': [], 'expectedFailures': [], 'unexpectedSuccesses': [], 'shouldStop': False, 'buffer': False, 'tb_locals': False, '_stdout_buffer': None, '_stderr_buffer': None, '_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>, '_original_stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, '_mirrorOutput': False, '_testRunEntered': False, '_moduleSetUpFailed': False, '_previousTestClass': <class '__main__.TestDemo02'>} |
TestSuite类中除了run()以外,还有一些重要的方法:addTest(test)和addTests(tests)。addTest(test)为添加测试用例,参数可以是TestCase或TestSuite的实例。addTests(tests)顾名思义,是对TestCase或TestSuite的实例的多个迭代进行添加,其内部实现原理依然是调用addTest(test)。简单点说,前者是添加单个测试用例,而后者是为了一次性添加多个测试用例。
依然沿用TestDemo01和TestDemo02的例子,执行代码做了一些调整,首先得到测试套件实例suite,分别调用addTest和addTests方法来添加测试用例,注意在addTests中只有一个参数,并且类型是元组,最后执行并输出测试结果的字典。这里也额外使用了另外一个TestSuite中的常用方法countTestCases(),作用是返回执行的测试方法的个数。
if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(TestDemo01('test02')) suite.addTests((TestDemo02('test04'),TestDemo02('test05'))) r = unittest.TestResult() suite.run(result=r) print(r.__dict__) print(suite.countTestCases()) |
执行结果不再赘述,仍然是依次执行添加的各个测试用例,然后输出总共执行的测试方法个数3。
This is test02. This is test04. This is test05. {'failfast': False, 'failures': [], 'errors': [], 'testsRun': 3, 'skipped': [], 'expectedFailures': [], 'unexpectedSuccesses': [], 'shouldStop': False, 'buffer': False, 'tb_locals': False, '_stdout_buffer': None, '_stderr_buffer': None, '_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>, '_original_stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, '_mirrorOutput': False, '_testRunEntered': False, '_moduleSetUpFailed': False, '_previousTestClass': <class '__main__.TestDemo02'>} 3 |
2.TestLoader
TestLoader是Unittest框架中的一个重要的类,可以从被测试的类和模块中创建测试套件,即TestSuite。通常我们不用对他进行实例化,使用匿名对象的方式来使用即可。TestLoader中提供了如下一些常用的方法。
(1)loadTestsFromTestCase(testCaseClass) :从某个类中加载所有的测试方法,参数为加载的类名,testCaseClass必须继承于TestCase。
(2)loadTestsFromModule(module, pattern=None) :从某个模块中加载所有的测试方法,参数为模块名,即文件名。当该模块中有多个类都继承于TestCase时,那么这些类里的测试方法均会被执行。
(3)loadTestsFromName(name, module=None) :加载某个单独的测试方法,参数name是一个string,格式为“module.class.method”。
(4)loadTestsFromNames(name, module=None) :names是一个list,用法与上面相同。
下面依然沿用前面计算器的例子来进行讲解。模块名为Test01.py,代码中被测试类为Calculator,TestDemo01与TestDemo02均为测试类,并继承于unittest.TestCase,每个测试类中各有一个测试方法。在执行的代码中,分别使用类名、模块名、方法的方式来加载要测试的方法,并添加到了测试集suite中,最后执行测试集。
import unittest # 导入当前的Test01模块,用于后续使用 from C03_Unittest.Ex03_TestLoader import Test01 class Calculator: def divide(self,x,y): return x / y class TestDemo01 (unittest.TestCase): def test01(self): print('This is test01.') cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,5) class TestDemo02(unittest.TestCase): def test02(self): print('This is test02.') cal = Calculator() result = cal.divide(0,2) self.assertEqual(result,0) if __name__ == '__main__': suite = unittest.TestSuite() # 加载该类 testCase01 = unittest.TestLoader().loadTestsFromTestCase(TestDemo01) # 加载整个模块 testCase02 = unittest.TestLoader().loadTestsFromModule(Test01) # 加载TestDemo01类中的测试方法test01 testCase03 = unittest.TestLoader().loadTestsFromName('C03_Unittest.Ex03_TestLoader.Test01.TestDemo01.test01') suite.addTests(testCase01) suite.addTests(testCase02) suite.addTests(testCase03) r = unittest.TestResult() suite.run(result=r) print(r.__dict__) |
运行结果如下,未出现失败和错误。仔细分析可以看到,由于先执行suite.addTests(testCase01), TestDemo01类中的方法被运行了,输出了“This is test01.”。接下来执行suite.addTests(testCase02),整个模块即Test01.py里的所有测试方法被执行,所以分别输出了“This is test01.”和“This is test02.”。最后加载的是testCase03,而他仅仅只是TestDemo01中的方法test01,所以又输出了一次“This is test01.”。
This is test01. This is test01. This is test02. This is test01. {'failfast': False, 'failures': [], 'errors': [], 'testsRun': 4, 'skipped': [], 'expectedFailures': [], 'unexpectedSuccesses': [], 'shouldStop': False, 'buffer': False, 'tb_locals': False, '_stdout_buffer': None, '_stderr_buffer': None, '_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>, '_original_stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, '_mirrorOutput': False, '_testRunEntered': False, '_moduleSetUpFailed': False, '_previousTestClass': <class 'C03_Unittest.Ex03_TestLoader.Test01.TestDemo01'>} |
其实在TestLoader中,还有其他的方法,比如discover,可以找到某个目录下的所有测试模块下的测试方法,这里就不详细介绍,请大家自行查看官网文档掌握其用法。
3.装饰器
在unittest中提供了装饰器的功能,比如我们想跳过某些方法的执行,或者直接设置某些方法为预期失败的,这时就会用到装饰器。对需要处理的方法的前一行加上诸如“@XX”,则可以实现相应的功能,常用的装饰器有以下几种。
(1)@unittest.skip(reason):无条件跳过测试,reason描述为什么跳过测试。
(2)@unittest.skipif(conditition,reason):condititon为条件,当条件为true时则跳过测试。
(3)@unittest.skipunless(condition,reason):condition为条件,与上面相反,当条件不是true时则跳过测试。
(4)@unittest.expectedFailure():标记该测试预期为失败 ,如果该测试方法运行失败,则该测试不算做失败。
沿用前面的例子,分别对4个方法设置不同的装饰器。
import unittest import sys class Calculator: def divide(self,x,y): return x / y class TestDemo (unittest.TestCase): def setUp(self): self.a = 10 self.b = 20 @unittest.skip('强制跳过') def test01(self): print('This is test01.') cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,3) @unittest.skipIf( 10 > 5, "满足条件则跳过") def test02(self): print('This is test02.') cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,3) @unittest.skipUnless( 10 > 5, "不满足条件则跳过") def test03(self): print('This is test03.') cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,3) @unittest.expectedFailure def test04(self): print('This is test04.') cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,3) if __name__ == '__main__': suite = unittest.TestSuite() # 加载整个类中的测试方法 testCase = unittest.TestLoader().loadTestsFromTestCase(TestDemo) suite.addTests(testCase) r = unittest.TestResult() suite.run(result=r) print(r.__dict__) |
运行结果如下。四个方法都分别设置了装饰器,可以看到被跳过的是前两个,第三个方法test03由于条件不满足,正常执行,第四个方法也执行了,但即使失败也没有计入到failures中。
This is test03. This is test04. {'failfast': False, 'failures': [(<__main__.TestDemo testMethod=test03>, 'Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex05_Skip/Test01.py", line 32, in test03 self.assertEqual(result,3) AssertionError: 5.0 != 3 ')], 'errors': [], 'testsRun': 4, 'skipped': [(<__main__.TestDemo testMethod=test01>, '强制跳过'), (<__main__.TestDemo testMethod=test02>, '满足条件则跳过')], 'expectedFailures': [(<__main__.TestDemo testMethod=test04>, 'Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex05_Skip/Test01.py", line 39, in test04 self.assertEqual(result,3) AssertionError: 5.0 != 3 ')], 'unexpectedSuccesses': [], 'shouldStop': False, 'buffer': False, 'tb_locals': False, '_stdout_buffer': None, '_stderr_buffer': None, '_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>, '_original_stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, '_mirrorOutput': False, '_testRunEntered': False, '_moduleSetUpFailed': False, '_previousTestClass': <class '__main__.TestDemo'>} |
4.TestResult
顾名思义,TestResult类是为了保存测试结果而专门设计的。从前面的例子可知,执行测试时最终都需要调用run函数,而run函数则必须传入一个参数result,这个result就是TestResult对象或者是其子类的对象。下面是run方法的源码实现,代码较长,只贴出一部分,目录是让大家明确result是run方法的必须参数。
def run(self, result, debug=False): topLevel = False if getattr(result, '_testRunEntered', False) is False: result._testRunEntered = topLevel = True ... ... return result |
我们单独贴出核心的代码段让大家理解,r为TestResult的实例,无论是以何种方式调用run方法,都需要将TestResult实例化的对象作为参数传递给run。
if __name__ == '__main__': suite = unittest.TestSuite() suite.addTests((TestDemo02('test04'),TestDemo02('test05'))) r = unittest.TestResult() suite.run(result=r) print(r.__dict__) |
TestResult的内容非常丰富,对测试结果做了详细的分类,下面的运行结果大家一定不会陌生,在前面的若干个例子里都出现过类似的部分。
{'failfast': False, 'failures': [], 'errors': [], 'testsRun': 3, 'skipped': [], 'expectedFailures': [], 'unexpectedSuccesses': [], 'shouldStop': False, 'buffer': False, 'tb_locals': False, '_stdout_buffer': None, '_stderr_buffer': None, '_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>, '_original_stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, '_mirrorOutput': False, '_testRunEntered': False,'_moduleSetUpFailed': False, '_previousTestClass': <class '__main__.TestDemo02'>} 3 |
TestResult的属性几乎涵盖了我们需要对当前测试了解的所有信息,下面抽取其中最重要的属性来进行解释。
(1)failfast:值为True或False,当设置为True时,测试过程中遇到失败或者错误,则立即终止后续的测试,通常我们保持False即可。
(2)failures:失败,这里会列出使用断言方法失败的情况。
(3)errors:错误,这里会列出程序出现的异常错误。
(4)testsRun:已经运行的所有测试的数量。
(5)skipped:列出跳过的测试方法及原因。
(6)expectedFailures:列出预期失败的方法。
(7)unexpectedSuccesses:列出标记为预期失败,但实际运行却又成功的方法。