UnitTest 测试框架 详解

540 阅读11分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第25篇文章,点击查看活动详情


UnitTest 模块

UnitTest模块是Python标准库中的模块,其模块提供了许多类和方法处理各种测试工作。先来了解几个概念。

- ·测试用例-testcase

这是UnitTest中最重要的概念,测试就是由一个个测试用例组成的,而对于测试框架来说测试用例就是最底层的东西,就像盖一座房子一样,砖头就是最基础的东西,无论怎么设计和搭建,最终都是要靠一块块砖头堆砌而成。测试用例既可以是对同一个测试点的不同输入,也可以是对不同测试点的不同输入,也可以是对多个测试点的组合测试,就看如何设计组合测试用例,但一般适用于前两者。

- ·测试固件-testfixture

测试固件从名字来说就是固定的测试代码,对于测试代码来说必然会有一些相同的部分,比如测试一个接口,那接口地址就是相同的部分,而这可以通过setup0进行初始化,然后各个测试用例直接调用初始化的接口地址就可以简化代码了。同样还可以通过teardown()来结束测试工作。测试固件就是整合了代码的公共部分。

- ·测试套件-testsuite

如果说测试用例是砖头的话,那测试套件就可以理解为一层楼的墙面。测试套件把多个测试用例集合到一起,而测试套件和测试用例一样,也可以有多个,并且可以组合在一起形成更多的测试用例集合。

- ·测试运行器-testrunner

测试运行器是给测试用例提供运行环境的,通过它的run0方法来执行测试用例,并在执行完成后将测试结果输出。

以上这些构筑了整个UnitTest的测试框架结构,接着就通过一个例子来介绍。

测试用例

在UnitTest模块中,需要通过继承TestCase类来构建单元测试用例,具体语法如下。

class 测试类名(unittest.TestCase): 

    测试用例

既可以一个测试用例生成一个类,也可以多个测试用例生成一个类,考虑到执行效率的问题,建议使用后者来构建测试用例。

class 测试类名(unittest.TestCase): 

    测试用例1

    测试用例2

    测试用例3

一个测试用例可以通过定义一个函数完成,将执行测试的代码封装到函数内,最后通过TaseCase类中的断言来判断测试是否通过,常用的断言方法有以下几种。

assertEqual(预期值,实际值)当两者相等的时候测试通过。

assertNotEqual(预期值,实际值)当两者不相等的时候测试通过。assertTrue(表达式)当表达式为真的时候测试通过。

assertFalse(表达式)当表达式为假的时候测试通过。

举个简单测试登录接口的例子,通过不同的输入来获取结果,然后用断言判断预期结果和实际结果是否相等。

实例代码:

1 import unittest 

2 import requests

3 class logintest(unittest.TestCase): 

4     def testlogin1(self):

5       ur1="http://www.xxx.com/login.htm1"

6       form = {"username":13111111111,"password" : 123456} 

7       r=requests.post(url,data=form) 

8       self.assertEqual(r.text,"登录成功”)

9     def testlogin2(self): 

10       url="http://www.xxx.cx.com/login.htm1" 

11       form = {"username":"", "paassword":123456) 

12       r=requests.post(url,data=form) 

13      self.assertEqual(r.text,"用户名不能为空”)

14    def testlogin3(self): 

15      url="http://www.xxx.xx.com/login.html"

16      form={"username:13111111111,"password":111111} 

17      r = requests.post(url,data=form) 

18      self.assertEqual(r.text,"密码不能为空")

19   def testlogin4(self):

20       url="http://www.xxx.com/login.html" 

21       form= form={"username':13111111111,"password":111111} 

22       r=requests.post(url,data = form) 

23       self.assertEqual(r.text,"账号或者密码错误”)

代码说明:

1 导入UnitTest模块。

3 定义一个logintest的测试类,这个类继承了UnitTest的TestCase的基类,这样就完成了UnitTest 的一个测试实例,这个实例可以包含多个测试用例,通过函数来完成每一个测试用例。

4~8定义testlogin10函数作为一个测试用例,该用例测试是否正常登录,即通过输入正确的账号和密码登录,增加断言判断是不是返回的信息“登录成功”,如果是,则测试用例通过,如果不是,则测试用例不通过。

9~13 定义 testlogin20函数作为一个测试用例,该用例测试异常登录,即通过空的账号和正确的密码登录,增加断言判断返回的是不是报错信息“用户名不能为空”,如果是,则测试用例通过,如果不是,则测试用例不通过。

14~18定义 testlogin30函数作为一个测试用例,该用例测试异常登录,即通过正确的账号和空的密码登录,增加断言判断返回的是不是报错信息“密码不能为空”,如果是,则测试用例通过,如果不是,则测试用例不通过。

19~23 定义 testlogin40函数作为一个测试用例,该用例测试异常登录,即通过正确的账号和错误的密码登录,增加断言判断返回的是不是报错信息“账号或者密码错误”,如果是,则测试用例通过,如果不是,则测试用例不通过。

这样就完成了测试用例的构建,4个测试用例分别测试登录接口的4种不同输入的结果,至于如何运行会在后面再介绍。

测试固件

之前介绍了测试固件就是将重复的代码放在一起,通过上一节的例子会发现,每个测试用例中的URL都是相同的,通过测试固

件的setup(可以将URL初始化,然后可以给各个测试用例调用。这样可以减少重复的代码,而且对于以后修改代码也有好处,只需要修改初始化的URL,而不需要再对每一个测试用例中的URL进行修改,那就把代码改一下

image.png

代码说明:

4~5在logintest类中定义setUp函数,这个函数就是放置测试用例的公共部分,类似一个全局变量,供其他函数调用。这里就是初始化一个接口的URL,让其他函数不需要再重复定义,直接通过变量self.url调用。

因为这是一个简单的测试实例,所以看上去代码只减少了2行,但对于公共部分比较多的情况就会发现减少了很多冗余的代码,也易于后期的维护,因此能利用测试固件的时候尽量使用。

测试套件

完成了测试用例的准备部分,接着需要根据用例进行组合,这时候就用到测试套件了。测试套件有多种添加测试用例的方式,下面介绍2种常用的添加方式。

第一种:

1 import unittest

2 import requests

3 class logintest(unittest.TestCase): 省略之前的代码

4 def suite(): 

5 loginTestcase=unittest.Testsuite()

6 loginTestCase.addTest (logintest("testlogin1")) 

7 loginTestCase.addTest (logintest("testlogin2"))

8 loginTestCase.addTest (logintest("testlogin3"))

9 loginTestCase.addTest (1ogintest("testlogin4"))

10 return loginTestCase

代码说明:

4 定义一个suite0函数,用来返回已经创建好的测试套件实例。

5 调用TestSuite(函数生成一个测试套件实例。

6~9 使用实例的addTest的方法,将logintest中的测试函数加入到测试套件中。

10 返回添加完测试用例的测试套件实例。

这样在完成了将测试用例加入到测试套件之中的过程,但这样一个个添加测试用例的方式有点烦琐,一旦用例太多就会有太多冗余的代码,于是介绍另外一个添加测试用例的方法,通过makeSuite方法来创建测试用例类中所有测试用例的测试套件。

实例代码:

image.png

代码说明:

5 通过makeSuite(函数将 logintest 中所有test开头的测试用例加入测试套件中。

只需要一行代码就能添加全部的测试用例到测试套件中,但缺点在于不灵活,只能添加全部,

当然如果在测试类中定义所有需要运行的测试用例,那用这个方法就相当简单了。

多个测试套件其实还可以通过TestSuite组合在一起,变成一个新的测试套件,来看一下如何实现。

image.png

代码说明:

6~8先把2个测试类的测试用例分别加入2个测试套件之中,然后通过TestSuite方法把2个测试套件合成一个测试套件实例。

这样通过测试套件把测试用例组合起来,就完成了大部分工作,最后只需要运行并获取测试报告就行了。

运行测试

UnitTest模块提供了TestRunner类,为测试的运行提供了环境,最常见的就是TextTestRunner 类,整个类使用了文字化的运行方式来报告最后的测试结果,来看例子。

image.png

代码说明:

7~8 使用TextTestRunner类构建一个运行器对象,此对象提供了run方法,它所接收的参数 是之前生成的测试套件实例,这样测试框架就可以自动运行测试套件中的测试用例了。

测试通过的运行结果如图所示。 即执行了4个测试用例,全部通过。

image.png

当然也有可能测试不通过,测试不通过的运行结果如图所示。

image.png

一样能看出也是运行了4个测试用例,但有一个测试失败了,fail的用例是testloginl,会打 印出失败原因,即实际运行结果与预期结果不符合。

其实还有一个更简单的运行方法,就是用UnitTest的main方法,这个方法是一个全局方法,即直接加载所有测试类中的测试用例,并全部执行,只需要一句代码就行。

1 import unittest 

2 import requests

3 class logintest(unittest.TestCase):

省略测试用例的代码

4 class loginouttest(unittest.TestCase): 省略测试用例的代码

5 def suite():

省略测试套件的代码 

6 if name =="main" 

7 unittest.main()

代码说明:

7用main方法完成所有测试用例的加载和运行,就是将所有的操作封装在main方法之中。结果和之前是一样的,这个方法虽然方便,却少了一些灵活性,需要通过实际情况来选择使用。

测试报告

UnitTest 测试框架作为Python内置的框架,也并非十分完善,虽然运行测试框架能看到结果, 但没有测试报告的输出,不易于测试结果的保存。那要如何获取测试报告呢?需要下载导入一个 第三方模块 HTMLTestRunner,这个类区别于之前的TextTestRunner类,是以HTML形式存放测 试结果的,并会以报告的形式保存。

HTMLTestRunner 扩展模块无法通过pip安装,下载地址如下。 tungwaiyip.info/software/HT…

现在完成之后需要将这个py文件放到Python安装的目录lib文件夹下面,由于这个扩展模块 是基于Python2开发的,那么对于Python3来说语法上会有不兼容,所以需要对这个文件进行修 改后才能使用。

第94行,将import StringIO 修改成import io

第539行,将StringIO.StringIO0 修改成io.StringIOQ 第631行,将print>>sys.stderr,“InTime Elapsed:%s”%(self.stopTime-self.startTime) 修改成print(sys.stderr,“InTimeElapsed: %s”%(se1f.stopTime-self.startTime))

第642行,将ifnot rmap.has_key(cls): 修改成if not cls in rmap:

第766行,将uo=

uo=o.decode(“1

ecode("latin-1”) 修改成

uo=e 第775行,将

e=e.decode("1 修改成

ue=e

第778行,将out

utput = saxutils.escape(uo+ue), 修改成out

tput = saxutil

ape(str(uo)+str(ue)).

修改完成之后就可以在Python3.6上使用这个扩展模块了。 实例代码:

image.png

代码说明:

8 新建一个名为resl.html的HTML,并且设置权限是读写。

9~10使用HTMLTestRunner模块中的HTMLTestRunner方法,构建一个运行器对象,并通过 参数将结果写入之前新建的resl.html文件之中,标题为测试报告,描述为详情,最后也是通过run 方法完成测试用例的运行。

运行之后会生成一个HTML的报告文件,报告模板如图所示。

image.png