python标准库系列教程(五)——unittest单元测试(中篇)
python进阶教程
机器学习
深度学习
长按二维码关注
进入正文
python标准库系列教程(五)——unittest库单元测试(中篇)
声明:前面的python标准库系列文章详细讲解了Python的三大函数式编程库以及集合库,itertools、functools、operators库以及collections库,本文继续python标准库系列文章,本文为第五篇,深入详解python的单元测试原理以及unittest的基本架构,鉴于篇幅较长,第五篇将分为几个小的篇章说明,本文为中篇,后续还会有系列文章更新,希望对小伙伴有所帮助!
上一篇已经详细讲解了TestCase、TestLoader、TestSuite这三者的使用方法,这篇文章将详细介绍剩下的几大构件,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,这样有可能导致一些未知的异常,如下:
AttributeError: '_io.TextIOWrapper' object has no attribute 'writeln'
本人测试过的,可能会显示但是还没有找到具体的解决办法,如果哪位大神有解决办法,望告知,将万分感谢。
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 翻译为,配置、装修的含义。
我们在测试的时候往往并不是像上面那样自,一成不变、从头到尾,可能会遇到点特殊的情况:如果我的测试需要在每次执行之前准备环境,或者在每次执行完之后需要进行一些清理怎么办?比如执行前需要连接数据库,执行完成之后需要还原数据、断开连接。总不能每个测试方法中都添加准备环境、清理环境的代码吧。幸好TestCase给我们提供了比较好的解决方案,这里主要涉及以下几个方法
setUp()
tearDown()
setUpClass()
tearDownClass()
我们通过在自定义的代码测试类(即继承自TestCase的类)中重新实现它们,来达到测试环境的搭建与销毁的目的。
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里面的。
如果我们不希望每次执行一个测试函数就执行一次“配置与清理”操作,而是想要在所有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了。
总结:
(1)unittest.skip也是一个装饰器,故而要使用@语法
(2)skip装饰器一共有三个 unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。
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使用。
通过查看定义,我们可以发现,单元测试运行的结果中,那些特殊的标记,比如成功是.,失败是E,异常是F,这些都是定义在TextTestResult里面的,包括OK,下划线------------------全都是定义在里面的。进一步我们查看,TextTestResult里面定义可以系列的以add开头的方法,如下:
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):
当单元测试通过、失败、遇到错误等问题时,这一些列的方法就是将相关的信息收集起来,将最终组织的文本保存下来。
需要注意的是,
(1)TextTestRunner的run方法所返回的一个对象就是TextTestResult的一个对象。
(2)我们一般不直接操作TextTestResult,而是操作TextTestRunner来达到想要的效果。
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文本文件,发现里面的结果和在控制台上的一模一样。即
test_add (test.TestFunctions)
这是测试add的测试方法 ... ok
test_divide (test.TestFunctions)
这是测试divide的测试方法 ... ok
test_minus (test.TestFunctions)
这是测试minus的测试方法 ... ok
test_multi (test.TestFunctions)
这是测试multi的测试方法 ... skipped '我不想测试它了'
----------------------------------------------------------------------
Ran 4 tests in 0.002s
OK (skipped=1)
06
unittest单元测试框架总结
测试流程:
1、第一步:创建测试类(继承TestCase):
(1)定义测试函数(以test开头),在函数中Assert
(2)定义配置fixture
setUp()、tearDown()、setUpClass()、tearDownClass()
(3)定义是否跳过测试
unittest.skip()、unittest.skipIf()、unittest.skipUnless
2、第二步:创建TestLoader和TestSuite
suite = unittest.TestSuite()
loader=unittest.TestLoader()
3、第三步:通过loader将Case传递到Suite里面去 suite.addTests(loader.loadTestsFromName('test.TestFunctions'))
4、第四步:创建runner,使用runner运行suite
上面的就是单元测试的一般架构和使用步骤了。
后面一篇文章会讲到关于 HTMLTestRunner 的使用哦,有兴趣的可以继续关注一下。
2019/01/09
Wednesday
小伙伴们,单元测试是任何编程语言都不可避免的哦,看完这篇文章你一定会有不一样的收获的,后面还有系列文章连载,请记得关注哦!如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!
推 荐 阅 读
python高级调试技巧(一)——原生态的pdb调试
python标准库系列教程(四)——collections库详细教程
python标准库系列教程(三)——operator库详细教程
python标准库系列教程(二)——functools (下篇)
python标准库系列教程(二)——functools (中篇)
python标准库系列教程(二)——functools (上篇)
赶紧关注我们吧
您的点赞和分享是我们进步的动力!
↘↘↘