本文基于接口自动化,用Excel里的数据来驱动测试。
数据驱动,即不用改变代码,只用修改Excel里面的测试数据,即能完成对Excel内用例的测试。
方法一:超继承
整体思路:读取Excel文件的测试数据,unittest的TestSuite通过实例化测试类的方法循环添加测试用例,最后通过HTMLTestRunner执行测试用例并把结果写入html页面;
py文件1:DoExcel类;通过openpyxl返回读取的Excel里的数据集合,返回格式为列表内嵌套字典,每一个字典表示一条测试用例;样例为:[{‘url’:'xxx','methods':'xxx'},{}]
1 import openpyxl 2 3 class DoExcel(): 4 def __init__(self,filename,sheet_name): 5 self.filename = filename 6 self.sheet_name = sheet_name 7 self.sheet_obj=openpyxl.load_workbook(filename=filename)[self.sheet_name] 8 self.max_row = self.sheet_obj.max_row 9 self.max_col = self.sheet_obj.max_column 10 11 def get_header(self): 12 header = [] 13 14 for i in range(1,self.max_col+1): 15 header.append(self.sheet_obj.cell(1,i).value) 16 return header 17 18 def get_data(self): 19 header = self.get_header() 20 datas = [] 21 for i in range(2,self.max_row+1): 22 row_data = {} 23 for j in range(1,self.max_col+1): 24 row_data[header[j-1]] = self.sheet_obj.cell(i,j).value 25 datas.append(row_data) 26 return datas
py文件2:
1、MyCookie类,用来管理cookie;
2、Http_requests类,封装了get/post方法,可以通过参数值进行相应的请求;
3、TestAPI单元测试类,继承unittest.TestCase类,重写__init__初始化函数,以及一个测试用例test_api;
为什么要重写初始化函数? 因为需要传入要执行的测试数据;
1 import unittest 2 import requests 3 4 5 class MyCookie(): 6 cookies = None 7 8 9 class Http_requests(): 10 11 def request(url,method='GET',data=None,cookies=None): 12 method = method.upper() 13 if method == 'GET': 14 res = requests.get(url,params=data,verify=False) 15 else : 16 res = requests.post(url, json=data,cookies=cookies,verify=False) 17 return res 18 19 20 class TestAPI(unittest.TestCase): 21 22 def __init__(self,methodName,url,methods,data,expected): 23 super(TestAPI,self).__init__(methodName) 24 self.url = url 25 self.methods = methods 26 self.data = data 27 self.expected = expected 28 29 @classmethod 30 def setUpClass(cls): 31 pass 32 33 def setUp(self): 34 pass 35 36 def test_api(self): 37 res = Http_requests.request(self.url,method=self.methods,data=self.data,cookies=getattr(MyCookie,'cookies',None)) 38 if res.cookies: 39 setattr(MyCookie,'cookies',res.cookies) 40 print('接口返回值为:{}'.format(res.json())) 41 try: 42 self.assertIn(self.expected, res.json()['msg']) 43 except AssertionError as e: 44 print('断言报错了') 45 raise e 46 47 def tearDown(self): 48 pass
py文件3:主方法,unittest测试套件发现测试用例,执行,并生成html测试报告;
1 import unittest 2 from cnpdx_test.HTMLTestRunner import HTMLTestRunner 3 from class_20220530_apitest import TestAPI 4 from DoExcel import DoExcel 5 6 7 if __name__ == '__main__': 8 suite = unittest.TestSuite() 9 10 loader = unittest.TestLoader() 11 datas = DoExcel('api_test.xlsx','python').get_data() 12 for data in datas: #将datas遍历,往suite中循环添加测试用例 13 suite.addTest(TestAPI('test_api', #一个TestCase的实例,都是一个测试用例;实例即调用构造函数,需要传入测试用例的名称; 14 data['url'], 15 data['methods'], 16 eval(data['data']), 17 data['expected'])) 18 19 with open('output.html','wb+') as file: 20 ht = HTMLTestRunner(stream=file, verbosity=2, title='API接口测试', description='测试登录接口、充值接口') 21 ht.run(suite)
知识点:
1、反射:操作对象/模块中的成员。
(1)获取;用法:getattr(object, name, default=None) 获取object对象中name属性的值,当这个属性不存在时则返回default的值;如果属性不存在,并且没有设置default,则会报错;如果例如上面py文件2中的getattr(MyCookie,'cookies',None)
(2)设置;用法:setattr(x, 'y', v) 给属性设置值。例如上面py文件2中的setattr(MyCookie,'cookies',res.cookies) 就是把登录接口返回的cookies的值设置到MyCookie类的cookies属性中;
(3)判断;用法:getattr(obj, name) 判断obj中是否有name属性,有返回True,没有返回False;
(4)删除;用法:delattr(obj, name) 删除obj中的name属性,相当于del obj.name;
2、eval():该函数将字符串转换为有效的表达式。例如mylist = “[1,2,3]”,eval(mylist)就能将其转换成列表;(当然不只是列表,字典等格式都是可以的)
3、超继承:上文中的TestAPI继承unittest.TestCase,为了不改变父类中的构造函数,在TestAPI中超继承了父类的构造函数super(TestAPI,self).__init__(methodName) 子类记得要传入父类构造函数需要的参数;
4、上下文管理器:with open('output/output.html', 'wb+') as file 避免了需要手动关闭文件的麻烦;注意这里需用wb+的方式打开文件;
方法二:使用ddt第三方模块
使用ddt,data driver test,数据驱动测试。ddt是python的第3方模块。
只需改动py文件2中的TestAPI单元测试类,即可。
ddt是通过注解来实现的:
(1)@ddt.ddt 用来装饰每个unittest.TestCase,即装饰类;
(2)@ddt.data(*values)用来装饰每个testcase,即装饰测试方法;
- 其中的参数前可以加*,表示解包。例如数据values是[{},{},{}]格式的,则解包后则是每个字典;(最多只能加一个*,没有两个*及以上的用法)
- 参数前也可以不用*,如上的values,则传入的实际就是列表;
- 程序会自动对解包后的数据遍历传给每个testcase,testcase需要用变量来接收遍历后的数据,并且数据需与遍历后的数据一一对应,且key的值都必须一致;
(3)@ddt.unpack 用来装饰测试方法testcase,对ddt.data中传入的数据进行解包;
数据values是[{},{},{}]格式的,同时使用了@ddt.data(*values)和@ddt.unpack,则testcase传入的参数需与字典内的key一一对应;
示例代码:
1 import unittest,ddt 2 3 mylist = [{"name":"li","sex":1},{"name":"wang","sex":2}] 4 5 @ddt.ddt 6 class Testap(unittest.TestCase): 7 8 @ddt.data(mylist) 9 def test_ddt_1(self,item): 10 print(item) # 打印1次,结果:[{'name': 'li', 'sex': 1}, {'name': 'wang', 'sex': 2}] 11 12 @ddt.data(*mylist) 13 def test_ddt_2(self, item): 14 print(item) # {'name': 'li', 'sex': 1} 15 #共打印2次 16 #第一次:{'name': 'li', 'sex': 1} 17 #第二次:{'name': 'wang', 'sex': 2} 18 19 @ddt.data(mylist) 20 @ddt.unpack 21 def test_ddt_3(self, item_1,item_2): #不给2个参数会报错; 22 print(item_1) #打印1次;打印:{'name': 'li', 'sex': 1} 23 print(item_2) #打印1次;打印:{'name': 'wang', 'sex': 2} 24 25 @ddt.data(*mylist) 26 @ddt.unpack 27 def test_ddt_4(self, name, sex): # 不给2个参数会报错,不是name和sex也会报错;!!!注意!!! 28 print(name) #打印2次:第一次:li 第二次:wang 29 print(sex) # 打印2次:第一次:1 第二次:2
因此,把py文件2中的TestAPI单元测试类修改,修改后代码为:
1 import unittest 2 import requests 3 import ddt 4 from tool.DoExcel import DoExcel 5 from tool.GetDirect import GetDirect 6 7 8 class MyCookie(): 9 cookies = None 10 11 12 class Http_requests(): 13 14 def request(url,method='GET',data=None,cookies=None): 15 method = method.upper() 16 if method == 'GET': 17 res = requests.get(url,params=data,verify=False) 18 else : 19 res = requests.post(url, json=data,cookies=cookies,verify=False) 20 return res 21 22 23 datas = DoExcel(GetDirect().test_data('api_test.xlsx')).get_data() 24 25 @ddt.ddt 26 class TestAPI(unittest.TestCase): 27 28 # def __init__(self,methodName,url,methods,data,expected): 29 # super(TestAPI,self).__init__(methodName) # 超继承,不改变父类的方法,只是额外增加了一些参数 30 # self.url = url 31 # self.methods = methods 32 # self.data = data 33 # self.expected = expected 34 35 @classmethod 36 def setUpClass(cls): 37 pass 38 39 def setUp(self): 40 self.excel = DoExcel(GetDirect().test_data('api_test.xlsx')) 41 pass 42 43 @ddt.data(*datas) #这里没有用unpack。因为unpack后,测试用例中传入的参数也必须增加,且值需一样,相对较麻烦,直接用字典取值更方便 44 def test_api(self, item): 45 testresults = '' #测试结果;PASS or FAIL 46 res = Http_requests.request(item['url'], item['methods'], eval(item['data']), cookies=getattr(MyCookie, 'cookies', None)) #eval的使用,否则值还是字符串,不是dict格式 47 if res.cookies: 48 setattr(MyCookie,'cookies',res.cookies) #反射的应用 49 print('接口返回值为:{}'.format(res.json())) 50 try: 51 self.assertIn(item['expected'], res.json()['msg']) 52 testresults = 'PASS' 53 except AssertionError as e: 54 # 断言不通过 55 # 回写到excel 56 # 抛出异常 57 print('断言报错了') 58 testresults = 'FAIL' 59 raise e 60 finally: 61 # 无论断言是否通过,都会执行finally里的代码 62 # 往excel里写断言结果、以及接口返回的json 63 row = self.excel.get_row_by_num(item['sheet_name'], item['case_id']) #根据用例编号获取在当前sheet表的行数 64 self.excel.write_excel(item['sheet_name'], row, testresults, str(res.json())) #回写测试结果到Excel,需将json转换为str 65 66 def tearDown(self): 67 pass
修改后注意run方法中,引入测试用例需要修改;从模块引入、从类引入都是可以的。
End.