Unittest高级应用
作者:强哥   类别:Python开发    日期:2019-01-29 17:52:35    阅读:1887 次   消耗积分:0 分

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):\n  File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex05_Skip/Test01.py", line 32, in test03\n    self.assertEqual(result,3)\nAssertionError: 5.0 != 3\n')], 'errors': [], 'testsRun': 4, 'skipped': [(<__main__.TestDemo testMethod=test01>, '强制跳过'), (<__main__.TestDemo testMethod=test02>, '满足条件则跳过')], 'expectedFailures': [(<__main__.TestDemo testMethod=test04>, 'Traceback (most recent call last):\n  File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex05_Skip/Test01.py", line 39, in test04\n    self.assertEqual(result,3)\nAssertionError: 5.0 != 3\n')], '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:列出标记为预期失败,但实际运行却又成功的方法。



为了答谢大家对蜗牛学院的支持,蜗牛学院将会定期对大家免费发放干货,敬请关注蜗牛学院的官方微信。


20181009_153045_341.jpg


   
版权所有,转载本站文章请注明出处:蜗牛笔记, http://www.woniunote.com/article/267
上一篇: 程序员薪资这么高,有封顶的吗?
下一篇: 今天,蜗牛学院全是人人人人人人…
提示:登录后添加有效评论可享受积分哦!
最新文章
    最多阅读
      特别推荐
      回到顶部