python标准库系列教程(五)——unittest单元测试(上篇)
python进阶教程
机器学习
深度学习
长按二维码关注
进入正文
python标准库系列教程(五)——unittest库单元测试(上篇)
声明:前面的python标准库系列文章详细讲解了Python的三大函数式编程库以及集合库,itertools、functools、operators库以及collections库,本文继续python标准库系列文章,本文为第五篇,深入详解python的单元测试原理以及unittest的基本架构,鉴于篇幅较长,第五篇将分为几个小的篇章说明,本文为上篇,后续还会有系列文章更新,希望对小伙伴有所帮助!
目录
一 pytho单元测试的基本概念
1.1 什么是“单元测试”
1.2 什么是“unittest”
1.3 unittest的基本架构
1.4 unittest的基本流程
二 unittest的简单实例——TestCase
2.1 使用unittest实现一个最基础的单
元测试
2.2 单元测试的详细信息输出
三 测试函数的组织——TestSuite
3.1 使用TestSuite进行Case的顺序组
织
3.2 使用TestLoader将要测试的Case
添加到TestSuite里面
3.3 两个最核心的方法讲解
四 unittest单元测试总结
4.1 单元测试的一般流程(三步走)
4.2 编写单元测试方法(以test开头)的
常用断言方法Assertion
五 下一篇预告
01
pytho单元测试的基本概念
什么是“单元测试”
什么是“unittest”
因为随着项目的不断扩大,单元测试在保证开发效率、可维护性和软件质量等方面的地位越发举足轻重,是一本万利的举措,单元测试保证我们所写的“单元”预期结果与真实结果是不是一样的,从而有利于我们发现错误。
python的unittest库是python自带的标准单元测试库,无需再自己安装。因为python语言一切皆对象,故而最小单元可以是“函数、类、模块”。
unittest的基本架构
框架的五个概念: 即test case、test suite、testLoader、test runner、test fixture。
(1)test case :一个完整的测试单元,执行该测试单元可以完成对某一个问题的验证,完整体现在:测试前环境准备(setUp),执行测试代码(run),以及测试后环境还原(tearDown);
(2)test suite :多个测试用例的集合,测试套件或测试计划;
(3)testLoader :加载TestCase到TestSuite中的,其中loadTestsFrom__()方法用于寻找TestCase,并创建它们的实例,然后添加到TestSuite中,返回TestSuite实例;
(4)test runner :执行测试用例,并将测试结果保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息;
(5)test fixture:一个测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;
这是unittest里面的5大核心构件,后面会一个一个进行讲解的。
unittest的基本流程
通过unittest类调用分析,可将框架的工作流程概况如下:
编写TestCase,由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite, 最后将运行的结果保存在TextTestResult中
02
unittest的简单实例——TestCase
使用unittest实现一个最基础的单元测试
比如我有一个模块functions,里面定义了4个函数分别实现加减乘除,现在我要测试这四个函数的正确与否。
def add(a, b):
return a+b
def minus(a, b):
return a-b
def multi(a, b):
return a*b
def divide(a, b):
return a/b
现在我要测试上面的四个方法,我们可以这样做,新建一个Python文件test.py,代码如下:
from functions import *
import unittest
class TestFunctions(unittest.TestCase):
def test_add(self): #测试加法的方法
self.assertEqual(3, add(1, 2))
self.assertNotEqual(3, add(2, 2))
def test_minus(self): #测试减法的方法
self.assertEqual(1, minus(3, 2))
def test_multi(self): #测试乘法的方法
self.assertEqual(6, multi(2, 3))
def test_divide(self): #测试除法的方法
self.assertEqual(2, divide(6, 3))
self.assertEqual(2.5, divide(5, 2))
if __name__ == '__main__':
unittest.main()
然后运行上面的test.py这个模块,即
python test.py 或者是
python -m unittest test.py (unittest模块是可以当做script去执行的哦)
打印出结果如下:
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
这里为什么会有这么多点,什么意思后面会讲解的。
单元测试的一般步骤总结:
第一步:选定需要测试的函数。比如上面的加减乘除四个方法;
第二步:定义一个测试类(核心)。继承自unittest.TestCase类,然后在类中定义实例测试方法,这里需要注意的是,测试方法一定要以“test”开头,表示这是一个测试方法,否则是不会进行运行的,当然为了习惯性地表示,和便于查看,一般就写成“test_被测试方法名”这种形式了,但是后面的下划线和被测试方法名称不是强制性的,只要保证是test开头的即可,否则是没有办法被unittest识别的哦。
第三步:通过unittest.main()来运行测试类。这里我们发现,没有构造自定义测试类的对象,更没有显示调用某个方法,但他就是运行了哦。main函数有一个verbosity 参数,该参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果,即没有上面的结果中的第1行(那几个点);如果设为 2,则输出详细的执行结果。
上面是一个python单元测试的一般步骤,当然还有很多其他的东西后面会讲到,因为我们将unittest有5大构建,这里还只讲了一个TestCase。
注意:上面的运行结果中,有四个点.... 那么是什么意思呢?
成功是 .,失败是 F,出错是 E,跳过是 S
单元测试的详细信息输出
上面的运行结果实在是太简单了,就几个点,我啥也看不明白,我们可以给每一个测试函数添加函数注释,并且在运行结果中显示出来,如下:
from functions import *
import unittest
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))
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.005s
OK
从上面可以看出,测试函数中的注释内容也打印了出来,即合理的使用注释和描述,能够使输出的测试报告更加便于阅读。
03
测试函数的组织——TestSuite
概念声明:在上面的例子中,自定义的TestFunctions是一个测试类,里面的每一个测试方法,即以test开头的方法,称之为一个Case。
反复运行上面的测试代码test.py,我们发现一个问题,我们所要测试的四个函数 add minus multi divide 它们每次在被测试的时候,被测试的顺序是不确定的,并时不时按照我在测试类中的定义那么顺序来执行的,可能是 add minus multi divide,也可能是add divide minus multi 等等。
这个时候我们就要用到TestSuite了。我们添加到TestSuite中的Case是会按照添加的顺序执行的。其实顾名思义,Suite的英文含义就是“套件、套装”,即由某一种规则或者是约束的东西。(注意:我们每一个要测试的方法称之为一个Case)
不仅如此,上面的例子中只有一个测试文件即test.py,我们直接执行该文件即可,但如果有多个测试文件,怎么进行组织,总不能一个个文件执行吧,多个测试类也是通过TestSuite来完成的。
所以,TestSuite是用来解决两类问题的:
第一:顺序测试问题
第二:多个测试类问题(不需要运行每一个测试脚本)
使用TestSuite进行Case的顺序组织
import unittest
from test import TestFunctions #导入我自己定义的测试类
if __name__ == '__main__':
# 第一步:构建需要测试的测试方法列表,按照列表中的顺序进行测试,不在列表中的则不进行测试
tests = [TestFunctions("test_add"), TestFunctions("test_minus"), TestFunctions("test_divide")]
# 第二步:构建TestSuite对象,并将Case列表添加进去
suite = unittest.TestSuite()
suite.addTests(tests)
#第三步:构建一个TextTestRunner对象,并且运行第二步中的suite对象
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
运行结果为:
test_add (test.TestFunctions)
这是测试add的测试方法 ... ok
test_minus (test.TestFunctions)
这是测试minus的测试方法 ... ok
test_divide (test.TestFunctions)
这是测试divide的测试方法 ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
从上面的执行结果可以看出来,suite里面的三个Case是按照顺序进行测试的,且没有添加进来的就不会测试。
注意:上面是先将Case组织成一个列表,然后一次性添加进Suite,我也可以以一个一个添加的,即:
suite.addTest(TestFunctions("test_multi")) #将test_multi测试函数添加进去。
总结:三步走
第一步:构建需要测试的测试方法列表,按照列表中的顺序进行测试,不在列表中的则不进行测试
第二步:构建TestSuite对象,并将Case列表添加进去
第三步:构建一个TextTestRunner对象,并且运行第二步中的suite对象
使用TestLoader将要测试的Case
添加到TestSuite里面
上面的例子,不管是一次性将所有的Case全部添加进Suite,还是一个一个添加,都是有序的,除此之外,我还可以使用TestLoader对象,这个也是unittest的5大核心构件之一,特也可以将Case加载到Suite里面去,代码如下:
import unittest
from test import TestFunctions #导入我自己定义的测试类
# 第一步:构造一个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.000s
OK
从上面可知,使用TestLoader加载的依然是没有顺序的,为什么呢?因为他不是按照顺序把每一个Case添加进suite,而是一次性将一个或者是好几个TestCase中的全部Case全部添加进去,至于顺序到底是怎样的,没办法确定。
总结:四步走
第一步:构造一个TestSuite对象
第二步:构造一个TestLoader对象
第三步:通过loader将TestFunctions里面所有的Case传递到Suite里面,故而是没有顺序的
第四步:构建一个TextTestRunner对象,并且运行第二步中的suite对象
两个最核心的方法讲解
上面那么多的这步骤,这哪里记得住啊,其实完全没有必要死记硬背,只需要掌握两组核心方法就可以了
(1)suite.addTests()和suite.addTest()
这两个方法是实现将Case添加进TestSuite的功能,
a)、直接用addTest方法添加单个Case
suite.addTest(TestFunctions("test_multi")) ,即suite.addTest(自定义测试类("以test开头的测试方法名"))
b)、直接用addTests添加过个Case或者是所有的Case
tests = [TestFunctions("test_add"), TestFunctions("test_minus"), TestFunctions("test_divide")]
suite.addTests(tests) #添加多个Case
也可以是
suite.addTests(loader.loadTestFrom***()),即它的参数也可以是loader的方法所返回的一个suite对象。
(2)loader.loadTestFrom***()系列方法——它包含四个方法
a)、loader.loadTestsFromModul(module) #参数为一个模块名,如本文中的test。它返回一个模块中所包含的所有的Cases组成的suite
b)、loader.loadTestsFromName('test.TestFunctions')) #参数为一个测试类名,但是要写成字符串的形式。它返回给定的测试类(如TestFunctions)中的所有的Cases所组成的suite。
c)、loader.loadTestsFromNames(['test.TestFunctions']) #参数为一个列表,列表的每一个元素都是由测试类名所组成的字符串。它返回一系列的测试类中所包含的所有的Cases所组成的suite.(这就解决多个测试类的问题,机上面所说的问题二)
d)、loader.loadTestsFromTestCase(TestFunctions) #参数为一个测试类,类中的所有的Cases所组成的suite。
04
unittest单元测试总结
单元测试的一般流程(三步走)
第一步:编写TestCase;
第二步:由TestLoader加载TestCase到TestSuite(这是无序的),也可以自己使用有序的方法,参见上面;
第三步:然后由TextTestRunner来运行TestSuite, 最后将运行的结果保存在TextTestResult中。关于TextTestResult会在系列文章的后面进行讲解。
编写单元测试方法(以test开头)的
常用断言方法Assertion
unittest库提供了很多实用方法来检测程序运行的结果和预期。包括三种类型的方法,每一种都覆盖了典型的类型,比如:
检查相等值、逻辑比较、异常
如果给定的Assertion通过了,那么测试会执行下一行代码。如果给定的assertion没有通过,测试会暂停并且生成错误信息。unittest库提供所有标准的xUnit assert方法。下面列出较重要方法的一部分
Method | Checks that | New in |
assertEqual(a, b) | a == b | |
assertNotEqual (a, b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a, b) | a is b | 3.1 |
assertIsNot(a, b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a, b) | a in b | 3.1 |
assertNotIn(a, b) | a not in b | 3.1 |
assertIsInstance (a, b) | isinstance(a, b) | 3.2 |
assertNotIsInstance (a, b) | not isinstance (a, b) | 3.2 |
Method | Checks that | New in |
assertRaises (exc, fun, *args, **kwds) | fun(*args, **kwds) raises exc | |
assertRaises- Regex (exc, r, fun, *args, **kwds) | fun(*args, **kwds) raises exc and t he message matches regex r | 3.1 |
assertWarns (warn, fun, *args, **kwds) | fun(*args, **kwds) raises warn | 3.2 |
assertWarns- Regex (warn, r, fun, *args, **kwds) | fun(*args, **kwds) raises warn and the message matches regex r | 3.2 |
assertLogs (logger, level) | The with block logs on logger with minimum level | 3.4 |
Method | Checks that | New in |
assertAlmost- Equal(a, b) | round(a-b, 7) == 0 | |
assertNot- AlmostEqual(a, b) | round(a-b, 7) != 0 | |
assertGreater (a, b) | a > b | 3.1 |
assertGreater- Equal(a, b) | a >= b | 3.1 |
assertLess(a, b) | a < b | 3.1 |
assertLess- Equal(a, b) | a <= b | 3.1 |
assertRegex(s, r) | r.search(s) | 3.1 |
assertNot- Regex(s, r) | not r.search(s) | 3.2 |
assertCount- Equal(a, b) | a and b have the same elements in the same number, regardless of their order | 3.2 |
Method | Used to compare | New in |
assertMulti-LineEqual (a, b) | strings | 3.1 |
assertSequence- Equal(a, b) | sequences | 3.1 |
assertList- Equal(a, b) | lists | 3.1 |
assertTupleEqual (a, b) | tuples | 3.1 |
assertSet- Equal(a, b) | sets or frozensets | 3.1 |
assertDict- Equal(a, b) | dicts | 3.1 |
Method Name | Deprecated alias | Deprecated alias | |
assertEqual() | failUnlessEqual | assertEquals | |
assertNot Equal() | failIfEqual | assertNot Equals | |
assertTrue() | failUnless | assert_ | |
assertFalse() | failIf | ||
assertRaises() | failUnlessRaises | ||
assertAlmost Equal() | failUnless AlmostEqual | assertAlmost Equals | |
assertNot AlmostEqual() | failIfAlmost Equal | assertNot AlmostEquals | |
assertRegex() | assertRegexp Matches | ||
assertNot Regex() | assertNot RegexpMatches | ||
assertRaises Regex() | assertRaises Regexp |
05
下一篇预告
限于篇幅,下一篇会接着本篇内容详细讲解TestRunner、TestFixture、TextTestResult相关的内容,有兴趣可以继续关注。
2019/01/08
Tuesday
小伙伴们,单元测试是任何编程语言都不可避免的哦,看完这篇文章你一定会有不一样的收获的,后面还有系列文章连载,请记得关注哦!如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!
推 荐 阅 读
python高级调试技巧(一)——原生态的pdb调试
python标准库系列教程(四)——collections库详细教程
python标准库系列教程(三)——operator库详细教程
python标准库系列教程(二)——functools (下篇)
python标准库系列教程(二)——functools (中篇)
赶紧关注我们吧
您的点赞和分享是我们进步的动力!
↘↘↘