python 单元测试(unittest)
自动化测试在各大互联网公司全面铺开,那么针对于自动化测试好的设计思想有哪些呢?.....今天我们共同探讨下Unittest之数据驱动(DDT是 “Data-Driven Tests”的缩写)。
对于接口自动化的数据驱动模式是大多数公司所选择的主流设计思想,有通过Mysql实现数据驱动,有通过Excel实现数据驱动,但是客观的认为,都没有Python模块中DDT模块所做的数据驱动方便,灵活。测试人员可以编写脚本进行自动化测试工作和接口回归测试,开发人员也可以进行提测之前的自测工作,保证代码质量。
什么是数据驱动呢?
数据驱动就是相同的测试脚本使用不同的测试数据来执行,测试数据和测试行为完全分离,这样的测试脚本设计模式称为数据驱动。例如,测试网站的登录功能,自动化测试工程师想验证不用的用户名和密码在网站登录时对系统影响结果,就可以使用数据驱动模式来进行自动化测试
一.安装ddt模块
unittest是python自带的单元测试框架所以不用安装但是由于ddt不是Python的标准库所以我们需要pip安装ddt模块(注:如果Python的Scripts目录已增加到环境变量,请忽略下方操作,直接pip3 install ddt安装即可。)
二.使用pycharm创建unittest文件
自动生成文件如下
import unittest #生成一个测试类(继承unittest.TestCase这个测试类) class MyTestCase(unittest.TestCase): def test_something(self): #结果断言 self.assertEqual(True, False) if __name__ == '__main__': unittest.main()
三.单元测试用例书写方法
例子1:
import unittest #请求方法 import requests class MyTestCase(unittest.TestCase): #每次方法执行之前执行 def setUp(self): print('==============起始============') #执行测试的函数 注意:所有执行测试的方法必须以test开头,执行顺序以后面的罗马数字升序执行 def test_01(self): print('这是第一个用例') #断言 True 等于 True self.assertEqual(True, True) def test_02(self): print('这是第二个用例') #断言 True 等于 True self.assertEqual(True, True) #每次方法执行之后执行 def tearDown(self): print('==============结束============') if __name__ == '__main__': unittest.main()
运行结果如下;
分析如下:
def setUp(self) 和 def tearDown(self)是unittest的内置方法,他们的意思是在执行用例的前后都被执行一次,每个用例在执行是都会被调用,相当于前置与后置处理方便我们自定义添加固定的方法,适用场景:请求前需要将加入时间戳是实时数据,请求后需要记录某个参数数据
例子2:
import unittest class MyTestCase(unittest.TestCase):
#必须装饰这个类 @classmethod def setUpClass(cls): print('最初执行一次') def test_01(self): print('这是用例1') self.assertEqual(True, True) def test_02(self): print('这是用例2') self.assertEqual(True, True) @classmethod def tearDownClass(cls): print('最后执行一次') if __name__ == '__main__': unittest.main()
运行结果如下:
分析如下:
def setUpClass(cls): 和 def tearDownClass(cls):同样也是unittest内置方法但要注意的是需要加上:@classmethod来进行装饰,它们的意思是在执行用例是只有最初和
最后会被执行,适用场景是:例如登录接口会返回一个token,将这个token提取出来放在header中,这样其他接口才可以正常访问
例子三:将unittest与requests模块结合 进行接口测试
1.将requests的post请求与get请求封装成一个类,便于在unittest中进行调用
import requests import json class RunMain(object): def get(self, url, data): res = requests.get(url=url, data=data).json() return res def post(self, url, data): res = requests.post(url=url, data=data).json() return res def run_main(self, url, method, data=None): res = None
#不区分大小写 if method.lower() == 'GET': res = self.get(url, data) else: res = self.post(url, data) return res
2.unittest中代码如下
import unittest #导入请求类 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化请求类 self.run=method.RunMain() def test_01(self): ''' 查询红包余额接口 get请求方法 :return: ''' print('这是第一个用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('通过') # else: # print('没通过') #对返回结果进行断言判断(内置断言) 提取的值,断言的值,匹配不上的输出信息 self.assertEqual(rep['successful'],1, '查询红包余额接口失败') # self.assertEqual()验证两个是否相等 # self.assertNotEqual() 判断不想等 # self.assertTrue()布尔类型判断 如果返回是True == True def test_02(self): ''' 豆瓣API,发送一条广播 :return: ''' print('这是第二个用例') url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'需要登录','发送广播失败') if __name__ == '__main__': unittest.main()
运行结果如下:
分析如下:
我们在setUp中初始化RunMain()便于在用例中引用,将url与data必填参数传参,然后通过self.assertEqual()对结果进行结果断言
例子4:探索unittest中接口用例如何上下参数关联
我们来想一想 如果下面的用例需要上面的用例的返回值 我们怎么做?
import unittest #导入请求类 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化请求类 self.run=method.RunMain() self.successful=self.test_01() def test_01(self): ''' 查询红包余额接口 get请求方法 :return: ''' print('这是第一个用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('通过') # else: # print('没通过') #对返回结果进行断言判断(内置断言) 提取的值,断言的值,匹配不上的输出信息 self.assertEqual(rep['successful'],1, '查询红包余额接口失败') return rep['successful'] # self.assertEqual()验证两个是否相等 # self.assertNotEqual() 判断不想等 # self.assertTrue()布尔类型判断 如果返回是True == True def test_02(self): ''' 豆瓣API,发送一条广播 :return: ''' print('这是第二个用例') print(self.successful) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'需要登录','发送广播失败') if __name__ == '__main__': unittest.main()
结果如下:
分析如下:
我们将想要提取的successful 在用例一的地方return出来 在从setup中实例化,这样做我们确实可以得到successful,但是我们需要重复的调用,因为每一个用例都要走setUp(),很显然这样是浪费资源的,所以我们要引入新的知识点:全局变量 globals(),注意:在unittest中用例的执行顺序是按照函数名的罗马数字顺序执行的
import unittest #导入请求类 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化请求类 self.run=method.RunMain() # self.successful=self.test_01() def test_01(self): ''' 查询红包余额接口 get请求方法 :return: ''' print('这是第一个用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('通过') # else: # print('没通过') #对返回结果进行断言判断(内置断言) 提取的值,断言的值,匹配不上的输出信息 self.assertEqual(rep['successful'],1, '查询红包余额接口失败') #将successful放入全局变量 globals()['successful'] =rep['successful'] # return rep['successful'] # self.assertEqual()验证两个是否相等 # self.assertNotEqual() 判断不想等 # self.assertTrue()布尔类型判断 如果返回是True == True def test_02(self): ''' 豆瓣API,发送一条广播 :return: ''' print('这是第二个用例') #从全局变量中获取 print(globals()['successful']) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'需要登录','发送广播失败') if __name__ == '__main__': unittest.main()
结果如下:
四.通过unittest管理用例
例子1:当我们有很多用例时,我们想要跳过一些case去执行,我们怎么去做呢?
当然我们可以直接把这个用例注释掉 但是这样太low啦,所以这就引入了新的知识点:@unittest.skip('test_02'),在case上引入这个装饰类 中间填写的就是这个case
的名称
代码如下:
import unittest #导入请求类 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化请求类 self.run=method.RunMain() # self.successful=self.test_01() def test_01(self): ''' 查询红包余额接口 get请求方法 :return: ''' print('这是第一个用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('通过') # else: # print('没通过') #对返回结果进行断言判断(内置断言) 提取的值,断言的值,匹配不上的输出信息 self.assertEqual(rep['successful'],1, '查询红包余额接口失败') #将successful放入全局变量 globals()['successful'] =rep['successful'] # return rep['successful'] # self.assertEqual()验证两个是否相等 # self.assertNotEqual() 判断不想等 # self.assertTrue()布尔类型判断 如果返回是True == True @unittest.skip('test_02') def test_02(self): ''' 豆瓣API,发送一条广播 :return: ''' print('这是第二个用例') #从全局变量中获取 print(globals()['successful']) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'需要登录','发送广播失败') if __name__ == '__main__': unittest.main()
结果如下:
例子2:我们现在执行程序还是通过unittest.main(),来执行MyTestCase下面的所有的用例,那么有没有别的方式来执行用例呢?
答案当然是有的,但是这种方式与main()方式来执行有什么区别呢?
#创建一个放用例的容器 suite=unittest.TestSuite() #需要往这个容器里面去添加case suite.addTest(MyTestCase('test_01')) suite.addTest(MyTestCase('test_02')) #执行(将我们的容器放进去) unittest.TextTestRunner.run(suite)
例子3:我们在书写用例的时候并不是一个人在写,项目组的小伙伴们也在写,不可能吧所有用例都写在一个py文件里,所以我们要引入一个新的知识点:
testLoader
代码如下:
from API.method_4_double import MyTestCase as MyTestCase2
# 此用法可以同时测试多个类
suite1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase)
suite2 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase2)
suite = unittest.TestSuite([suite1, suite2])
unittest.TextTestRunner(verbosity=2).run(suite)
结果如下:
下面针对上述脚本中应用到的unittest模块下的几个成员进行简单的介绍,以便于理解上述代码:
TestCase:所有测试用例的基本类,给一个测试方法的名字,就会返回一个测试用例实例;
TestSuit:组织测试用例的实例,支持测试用例的添加和删除,最终将传递给 testRunner进行测试执行;
TextTestRunner:进行测试用例执行的实例,其中Text的意思是以文本形式显示测试结果。测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息;
TestLoader:用来加载TestCase到TestSuite中的,其中有几个 loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例;
五.使用unittest和HTMLTestRunner结合生成测试报告
我们现在查看测试结果都是通过pycharm中的控制台来查看,这样的查看方式只能是测试开发人员而普通的功能测试人员不能随时的查看结果也不方法我们发送测试报告,所以我们要将我们的测试结果生成一个html页面方便查看与发送测试报告以便将测试结果更加直观的展示出来
下载网上一个开源的HTMLTestRunner.py文件,这个是生成报告的模板文件
下载地址:https://www.cnblogs.com/feiquan/p/8525903.html
重点:当我们下载下来的时候 我们要办这个文件放在python安装目录
/Users/wangsen/miniconda3/lib/python3.5/site-packages/HTMLTestRunner.py 这样就大功告成了
现在我们来使用HTMLTestRunner来生成测试报告
代码如下:
import unittest
from API import method_5
import HTMLTestRunner
################以一个类的维度控制测试用例的执行#############
cases=unittest.TestLoader().loadTestsFromTestCase(method_5.MyTestCase)
mysuite=unittest.TestSuite([cases])
filename = '/Users/wangsen/PycharmProjects/lufei_learn/report/test.html'
#一二进制方式打开文件,准备写
file_object=open(filename,'wb')
#使用HTMLTestRunner配置参数,输出报告路径,报告标题,描述,均可以配置
runner=HTMLTestRunner.HTMLTestRunner(
stream=file_object,
title='报告主题:接口测试报告',
description='报告详细描述',
)
#运行测试集合
runner.run(mysuite)
结果如下:
7.DDT数据驱动执行接口测试
- 代码复用率高。同一测试逻辑编写一次,可以被多条测试数据复用,提高了测试代码的复用率,同时可以提高测试脚本的编写效率。
- 异常排查效率高。测试框架依据测试数据,每条数据生成一条测试用例,用例执行过程相互隔离,在其中一条失败的情况下,不会影响其他的测试用例。
- 代码的可维护性高。清晰的测试框架,利于其他测试工程师阅读,提高了代码的可维护性。
1.以元组, 列表,字典传递数据
好了 ,上代码:
import unittest #导入ddt的模块 from ddt import ddt,data,unpack from basis import method #用ddt来修饰我们的类 @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() #引入data来修饰我们的数据,参数化数据 @data(('查询红包余额接口','get','http://ios.wecash.net/biz/wallet/amount','CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29','successful',1), ('发送一条广播','post','https://api.douban.com/shuo/statuses/',"{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}",'msg','需要登录')) #导入参数 @unpack def test_something(self,name,method,url,data,assertion,value): print('测试接口:%s'%name) rep=self.run.run_main(url,method,data) print(rep) self.assertEqual(rep[str(assertion)],value,'测试不通过') if __name__ == '__main__': unittest.main()
代码解析:
如果我们要引用ddt实现数据驱动 首先需要导入模块from ddt import ddt,data,unpack
@ddt是用来是用来装饰unittest类的
传入元组、字典、列表等复杂结构数据,@data 方法结合 @unpack方法使用
2.以文件作为数据传递@file_data
代码如下
import unittest from basis import method from ddt import ddt,file_data #装饰ddt @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() #导入file_data()获取json中的数据,在test_something测试函数中接受 @file_data("test_data_list.json") def test_something(self,message): name, method, url, data, assertion, value=message[0],message[1],message[2],message[3],message[4],message[5] print("测试接口为:%s"%name) rep=self.run.run_main(url,method,data) print(rep) self.assertEqual(rep[assertion],value,'测试不通过') if __name__ == '__main__': unittest.main()
json文件格式如下
[ ["查询红包余额接口","get","http://ios.wecash.net/biz/wallet/amount","CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29","successful",1], ["发送一条广播","post","https://api.douban.com/shuo/statuses/","{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}","msg","需要登录"] ]
3.还有最后一种方式 那就是从excel中读取数据
代码如下:
import unittest from basis import method from ddt import ddt,data,unpack,file_data from API.ExcelUtil import ParseExcel excelPath='/Users/wangsen/PycharmProjects/lufei_learn/API/接口自动化测试数据.xlsx' sheetName='接口数据表' #创建ParseExcel类的实例对象 excel=ParseExcel(excelPath,sheetName) @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() # 导入excel的数据(如果@ddt.data()括号中传的是一个方法,方法前需要加星号(*)) @data(*excel.getDatasFromSheet()) def test_something(self,message): name, method, url, data, assertion, value=tuple(message) print("测试接口为:%s" % name) rep = self.run.run_main(url, method, data) print(rep) self.assertEqual(rep[assertion], value, '测试不通过') if __name__ == '__main__': unittest.main()
读取excel数据的代码如下:
import xlrd import traceback class ParseExcel(object): #excel路径,sheet页名称 def __init__(self,excelPath,sheetName): try: #将读取得excel加载到内存 self.wb=xlrd.open_workbook(excelPath) except Exception as e: print(traceback.format_exc()) else: #通过工作表名称获取一个工作表对象 self.sheet=self.wb.sheet_by_name(sheetName) #获取工作表中存在数据的区域的最大行号 self.maxRowNum=self.sheet.nrows def getDatasFromSheet(self): #用于存放从工作表中读取出来的数据 dataList=[] #因为工作表中的第一行是标题行,所以需要去掉 for i in range(1,self.maxRowNum): #行内容 列表类型 row = self.sheet.row_values(i) if row: temList=[] temList.append(row[1]) temList.append(row[2]) temList.append(row[3]) temList.append(row[4]) temList.append(row[5]) temList.append(row[6]) dataList.append(temList) # print(dataList) return dataList
8.将unittest于Jenkins结合并发送报告邮件(自动化测试项目)
我们已经将unittest如何管理case如何生成报告如何进行数据驱动测试都已经讲了,现在讲一下如何将unittest变成python自动化测试框架。
本着一切都往高大上走的原则,我们进行如下设计:
开发语言:python
应用模块:requests
case管理:unittest框架
生成测试报告:HTMLTestRunner
数据如何存储管理:可以用mysql管理,excel管理,json管理
如何进行测试:Jenkins+unittest进行持续集成
问题
如何管理case(如何跳过case,case写在哪)
如何case执行(如何管理,顺序先后不是放在前面就先执行而是根据case命名的升序来执行)
如何解决case的依赖(定义全局变量,也可以放在配置文件里也可以放在数据库里都是可以的)
如何生成测试报告(放在安装目录 lib下面,py2到py3一些修改)