我报名参加金石计划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进行修改,那就把代码改一下
代码说明:
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方法来创建测试用例类中所有测试用例的测试套件。
实例代码:
代码说明:
5 通过makeSuite(函数将 logintest 中所有test开头的测试用例加入测试套件中。
只需要一行代码就能添加全部的测试用例到测试套件中,但缺点在于不灵活,只能添加全部,
当然如果在测试类中定义所有需要运行的测试用例,那用这个方法就相当简单了。
多个测试套件其实还可以通过TestSuite组合在一起,变成一个新的测试套件,来看一下如何实现。
代码说明:
6~8先把2个测试类的测试用例分别加入2个测试套件之中,然后通过TestSuite方法把2个测试套件合成一个测试套件实例。
这样通过测试套件把测试用例组合起来,就完成了大部分工作,最后只需要运行并获取测试报告就行了。
运行测试
UnitTest模块提供了TestRunner类,为测试的运行提供了环境,最常见的就是TextTestRunner 类,整个类使用了文字化的运行方式来报告最后的测试结果,来看例子。
代码说明:
7~8 使用TextTestRunner类构建一个运行器对象,此对象提供了run方法,它所接收的参数 是之前生成的测试套件实例,这样测试框架就可以自动运行测试套件中的测试用例了。
测试通过的运行结果如图所示。 即执行了4个测试用例,全部通过。
当然也有可能测试不通过,测试不通过的运行结果如图所示。
一样能看出也是运行了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上使用这个扩展模块了。 实例代码:
代码说明:
8 新建一个名为resl.html的HTML,并且设置权限是读写。
9~10使用HTMLTestRunner模块中的HTMLTestRunner方法,构建一个运行器对象,并通过 参数将结果写入之前新建的resl.html文件之中,标题为测试报告,描述为详情,最后也是通过run 方法完成测试用例的运行。
运行之后会生成一个HTML的报告文件,报告模板如图所示。