查看原文
其他

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

草yang年华 机器学习与python集中营 2021-09-10

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单元测试的基本概念


什么是“单元测试”

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。


什么是“unittest”

unittest原名为PyUnit,是由java的JUnit衍生而来。unittest是xUnit系列框架中的一员,如果你了解xUnit的其他成员,那你用unittest来应该是很轻松的,它们的工作方式都差不多。对于单元测试,需要设置预先条件,对比预期结果和实际结果。为什么要这么做呢?
因为随着项目的不断扩大,单元测试在保证开发效率、可维护性和软件质量等方面的地位越发举足轻重,是一本万利的举措,单元测试保证我们所写的“单元”预期结果与真实结果是不是一样的,从而有利于我们发现错误。
python的unittest库是python自带的标准单元测试库,无需再自己安装。因为python语言一切皆对象,故而最小单元可以是“函数、类、模块”


unittest的基本架构

先从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(12))
        self.assertNotEqual(3, add(22))

    def test_minus(self): #测试减法的方法
        self.assertEqual(1, minus(32))

    def test_multi(self): #测试乘法的方法
        self.assertEqual(6, multi(23))

    def test_divide(self): #测试除法的方法
        self.assertEqual(2, divide(63))
        self.assertEqual(2.5, divide(52))

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(12))
        self.assertNotEqual(3, add(22))

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

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

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

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&nbsp;(下篇)

python标准库系列教程(二)——functools&nbsp;(中篇)

python标准库系列教程(二)——functools&nbsp;(上篇)

python标准库系列教程(一)——itertools


赶紧关注我们吧

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

↘↘↘

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存