unittest接口测试:数据驱动的两种实现(一个测试用例跑多份测试数据)

本文基于接口自动化,用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
View Code

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
View Code

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)
View Code

知识点:

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
View Code

因此,把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
View Code

修改后注意run方法中,引入测试用例需要修改;从模块引入、从类引入都是可以的。

 

End.

posted @ 2022-06-08 17:16  youreyebows  阅读(286)  评论(0编辑  收藏  举报