• 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.

  • 相关阅读:
    Lua中的closure、泛型for
    Lua多重继承
    (转)C++ new详解
    C++重载操作符学习
    Lua中使用继承来组装新的环境
    DOS:变量嵌套和命令嵌套
    C++中成员的私有性
    ManualResetEvent 类
    在IIS中部署和注册WCF服务
    ArcGIS Server 10 地图缓存新特性
  • 原文地址:https://www.cnblogs.com/youreyebows/p/16334544.html
Copyright © 2020-2023  润新知