python标准库系列教程(五)——unittest单元测试(中篇)

640?wx_fmt=gif

640?wx_fmt=jpeg

python进阶教程

机器学习

深度学习

长按二维码关注

进入正文


640?wx_fmt=png

python标准库系列教程(五)——unittest库单元测试(中篇)

声明:前面的python标准库系列文章详细讲解了Python的三大函数式编程库以及集合库,itertools、functools、operators库以及collections库,本文继续python标准库系列文章,本文为第五篇,深入详解python的单元测试原理以及unittest的基本架构,鉴于篇幅较长,第五篇将分为几个小的篇章说明,本文为中篇,后续还会有系列文章更新,希望对小伙伴有所帮助!TestRunner、TestFixture、TextTestResult这三者的用法。


目录

一 从单元测试的“运行代码”说起

  1.1 最常见的写法:

       unisttest.main(verbosity=2)

   1.2 TestCase提供的run方法

   1.3 TestSuite提供的run方法

   1.4 TextTestRunner提供的run方法

二 TextTesttRunnner

       ——文本测试运行器

   2.1 TextTestRunner的run方法

三 测试环境的搭建与销毁

      ——testfixture(本质是TestCase方法的重写)

   3.1 setUp()和tearDown()方法

  3.2 setUpClass()和tearDownClass()方法

   3.3 跳过某一个Case不测试

四 TextTestResult的使用

五 将单元测试的结果保存在文件中

六 unittest单元测试框架总结

01

从单元测试的“运行代码”说起

 单元测试的代码依旧是需要运行的,只不过这个运行不是像普通函数那样,需要调用某一个对应的函数,然后执行它,那么单元测试的运行入口是什么呢?要运行单元测试代码,有很多种不同的方法。

最常见的写法:

unisttest.main(verbosity=2)   

这是unittest提供的最直观简单的调用入口,它的底层设计涉及到了,TestCase、TestSuite、TestRunner、TextTestResult这几个类的综合应用,这里就不再做源码分析了。

实现代码,参见上一篇文章哦。


TestCase提供的run方法   

我们要进行单元测试,其实就是要运行TestCase里面的测试函数,TestCase提供了一个run函数是可以完成这样的操作的。

如下代码:

result = unittest.TextTestResult(f,'it is test result',verbosity=2) #初始化TextTestResult类实例

testcase = TestFunctions('test_multi')  #初始化TestFunctions类实例
testcase(result) #跟testcase.run(result)的结果是一样的,我们只需要传入一个result对象即可

 TestSuite提供的run方法 

suite = unittest.TestSuite()

loader=unittest.TestLoader()

suite.addTests(loader.loadTestsFromName('test.TestFunctions'))

result = unittest.TestResult(sys.stdout,''it is test',2) #初始化TextTestResult类实例

suite.run(result)  #调用suite的run方法

 TextTestRunner提供的run方法 

参见下一章节要讲的。

总结:单元测试代码的运行推荐的运行方式为(1)和(4)


02

TextTesttRunnner ——文本测试运行器

unittest系列文章的上篇,在介绍TestSuite和TestLoader的时候,我们使用了TextTestRunner,但是当时并没有详细解释它到底是什么意思,顾名思义,它的意思是专门提供“测试代码的运行的”。虽然我们使用TestCase和TestSuite的run方法也可以运行单元测试代码,但是我们一般不这样去做,一方面是这样做需要需要手动构造一个TextTestResult的对象result,这样有可能导致一些未知的异常,如下:


TextTestRunner的run方法

该方法是unittest运行代码最主要的方式,他可以运行一个Case,也可以运行一系列的Case,也可以运行一个Suite,

如下所示:

(1)测试一个Case

if __name__ == '__main__':
    runner=unittest.TextTestRunner(verbosity=2)
    testcase = TestFunctions('test_multi')  #初始化TestFunctions类实例,此处之测试一个multi
    runner.run(testcase)  #运行测试

'''运行结果为:
test_multi (__main__.TestFunctions)
这是测试multi的测试方法 ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
'''

(2)测试一整个TestCase,即TestSuite

if __name__=='__main__': 
    #第一步:构造TestSuite对象
    suite = unittest.TestSuite()

    #第二步:构造一个TestLoader对象
    loader=unittest.TestLoader()

    #第三步:通过loader将Case传递到Suite里面
    suite.addTests(loader.loadTestsFromName('test.TestFunctions'))

    #第四步:构建一个TextTestRunner对象,并且运行第二步中的suite对象
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

'''运行结果为:
test_add (test.TestFunctions)
这是测试add的测试方法 ... ok
test_divide (test.TestFunctions)
这是测试divide的测试方法 ... ok
test_minus (test.TestFunctions)
这是测试minus的测试方法 ... ok
test_multi (test.TestFunctions)
这是测试multi的测试方法 ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.004s
OK
''''


03

测试环境的搭建与销毁

测试环境的搭建与销毁——testfixture(本质是TestCase方法的重写)

fixture 翻译为,配置、装修的含义。


       setUp()和tearDown()方法         

from functions import *
import unittest
import sys

class TestFunctions(unittest.TestCase):
    """这是其自己编写测试类"""

    def setUp(self):
        print("在测试之前,需要做一些准备工作.")

    def tearDown(self):
        print("做完测试之后做一些清理工作.")

    def test_add(self):  #测试加法的方法
        """这是测试add的测试方法"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self): #测试减法的方法
        """这是测试minus的测试方法"""
        self.assertEqual(1, minus(3, 2))

    def test_multi(self): #测试乘法的方法
        """这是测试multi的测试方法"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self): #测试除法的方法
        """这是测试divide的测试方法"""
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

if __name__ == '__main__':
    unittest.main(verbosity=2)

'''运行结果为:
test_add (__main__.TestFunctions)
这是测试add的测试方法 ... 在测试之前,需要做一些准备工作.
做完测试之后做一些清理工作.
ok
test_divide (__main__.TestFunctions)
这是测试divide的测试方法 ... 在测试之前,需要做一些准备工作.
做完测试之后做一些清理工作.
ok
test_minus (__main__.TestFunctions)
这是测试minus的测试方法 ... 在测试之前,需要做一些准备工作.
做完测试之后做一些清理工作.
ok
test_multi (__main__.TestFunctions)
这是测试multi的测试方法 ... 在测试之前,需要做一些准备工作.
做完测试之后做一些清理工作.
ok
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
'''
总结:

(1)在每一个测试方法(以test开头的方法)执行之前,总是会先调用自定义的setUp方法进行相关的准备工作与环境配置;

在每一个测试方法执行完之后,总是会调用tearDown方法进行环境的清理;

(2)为什么称之为TestFixture呢?取这个名字的初衷不得而知,但是并不存在TestFixture这个类,但是这两个方法并不是定义在

TestFixture里面的,它是定义在基类TestCase里面的。


   setUpClass()和tearDownClass()方法    

如果我们不希望每次执行一个测试函数就执行一次“配置与清理”操作,而是想要在所有Case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用 setUpClass() 与 tearDownClass()这两个方法。

from functions import *
import unittest
import sys

class TestFunctions(unittest.TestCase):
    """这是其自己编写测试类"""

    @classmethod
    def setUpClass(self):
        print("在测试之前,需要做一些准备工作,只做这一次.")

    @classmethod
    def tearDownClass(self):
        print("做完测试之后做一些清理工作,只做这一次.")

    '''其他代码和上面一样,此处省略'''

if __name__ == '__main__':
    unittest.main(verbosity=2)

'''运行结果为:
在测试之前,需要做一些准备工作,只做这一次.
test_add (__main__.TestFunctions)
这是测试add的测试方法 ... ok
test_divide (__main__.TestFunctions)
这是测试divide的测试方法 ... ok
test_minus (__main__.TestFunctions)
这是测试minus的测试方法 ... ok
test_multi (__main__.TestFunctions)
这是测试multi的测试方法 ... ok
做完测试之后做一些清理工作,只做这一次.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
'''

总结:

(1)这是两个类方法,必须使用@classmethod装饰器;

(2)在所有的测试方法运行之前,进行配置,在所有的测试函数运行结束之后,进行清理,需要注意的是这两个方法只会在开头和结尾执行一次;


  跳过某一个Case不测试      

如果我们在测试的时候,我们可能会不想对某一个方法进行测试,有人会说,那你不想测试某一个方法,就不要把它load进入suite里面不就行了嘛,这当然是没问题的,但是如果我们已经添加进去了,但是我又不想测试它了,这该怎么办呢?

比如我不想测试乘法函数multi,我们可以这样做。

from functions import *
import unittest
import sys

class TestFunctions(unittest.TestCase):
    """这是其自己编写测试类"""

    def test_add(self):  #测试加法的方法
        """这是测试add的测试方法"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self): #测试减法的方法
        """这是测试minus的测试方法"""
        self.assertEqual(1, minus(3, 2))

    @unittest.skip('我不想测试它了')      #表示不想测试的
    def test_multi(self): #测试乘法的方法
        """这是测试multi的测试方法"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self): #测试除法的方法
        """这是测试divide的测试方法"""
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

if __name__ == '__main__':
    unittest.main(verbosity=2)

'''运行结果为:
test_add (__main__.TestFunctions)
这是测试add的测试方法 ... ok
test_divide (__main__.TestFunctions)
这是测试divide的测试方法 ... ok
test_minus (__main__.TestFunctions)
这是测试minus的测试方法 ... ok
test_multi (__main__.TestFunctions)
这是测试multi的测试方法 ... skipped '我不想测试它了'
----------------------------------------------------------------------
Ran 4 tests in 0.003s
OK (skipped=1)
'''

可以看到总的test数量还是4个,但multi()方法被skip了。总结:


04

TextTestResult的使用


TextTestResult:测试的结果会保存到TextTestResult实例中,包括运行用例数,成功数,失败数等。

class TextTestResult(result.TestResult):
    """A test result class that can print formatted text results to a stream.
    Used by TextTestRunner.

由此可见,它是继承自TestResult的,这个类的作用是打印格式化的文本结果到流中,经常被runner使用。

class TextTestResult(result.TestResult):
    """A test result class that can print formatted text results to a stream.
    Used by TextTestRunner.
    """
    separator1 = '=' * 70
    separator2 = '-' * 70

    def __init__(self, stream, descriptions, verbosity):

    def getDescription(self, test):

    def startTest(self, test):

    def addSuccess(self, test):

    def addError(self, test, err):

    def addFailure(self, test, err):

    def addSkip(self, test, reason):

    def addExpectedFailure(self, test, err):

    def addUnexpectedSuccess(self, test):

    def printErrors(self):

    def printErrorList(self, flavour, errors):

当单元测试通过、失败、遇到错误等问题时,这一些列的方法就是将相关的信息收集起来,将最终组织的文本保存下来。需要注意的是,


05

将单元测试的结果保存在文件中


前面所讲的实例中,单元测试的结果只能输出到控制台,这样没有办法查看之前的执行记录,我们想将结果输出到文件。操作如下:

if __name__ == '__main__':
    suite = unittest.TestSuite()
    loader=unittest.TestLoader()
    suite.addTests(loader.loadTestsFromName('test.TestFunctions'))

    with open('result.txt', 'a') as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2) #需要为stream指定文件
        runner.run(suite)

然后我们打开那个result文本文件,发现里面的结果和在控制台上的一模一样。即


06

unittest单元测试框架总结


测试流程:1、第一步:创建测试类(继承TestCase):2、第二步:创建TestLoader和TestSuite

                loader=unittest.TestLoader()3、第三步:通过loader将Case传递到Suite里面去               suite.addTests(loader.loadTestsFromName('test.TestFunctions'))4、第四步:创建runner,使用runner运行suite


640?wx_fmt=png

2019/01/09

Wednesday

小伙伴们,单元测试是任何编程语言都不可避免的哦,看完这篇文章你一定会有不一样的收获的,后面还有系列文章连载,请记得关注哦!如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!

推 荐 阅 读


640?wx_fmt=jpeg

赶紧关注我们吧

您的点赞和分享是我们进步的动力!

↘↘↘

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值