接口自动化测试框架
python+unittest+requests+ddt+openpyxl+pymysql+logging+unittestreport+jenkins
项目结构
|-- conf # 配置文件层 | |-- setting.py # 配置文件,存放数据库连接信息、接口地址等全局配置 |-- logs #日志管理层 | |-- test.log # 日志文件,记录测试过程中的详细信息 |-- test_datas # 数据管理层 | |-- test_data.xlsx # Excel 文件,用于存储测试数据 |-- test_cases # 测试用例层 | |-- test_1_login.py # 登陆 | |-- test_2_upload.py # 图片上传 | |-- test_3_create_product.py # 创建商品 | |-- test_4_member_register.py # 用户注册 | |-- test_5_member_login.py # 用户登陆 | |-- test_6_place_order.py # 用户下单 | |-- test_7_unittest_all.py # 集成所有用例 |-- tools # 工具层 | |-- handle_excel.py # 工具类,测试数据操作封装 | |-- handle_db.py # 工具类,数据库操作封装,用pymysql连接mysql数据库,操作数据库等一系列方法,进行数据校验; | |-- handle_phone.py # 工具类,封装一个可以通过faker库造数据生成随机手机号的方法 | |-- handel_extract.py # 工具类,从接口响应结果中提取全局变量用于:鉴权、参数依赖提取 | |-- handle_replace.py # 工具类,封装一个参数替换的工具,对请求参数进行处理,返回可以直接发送请求的参数 | |-- handle_path.py # 工具类,存放路径封装 | |-- handle_requests.py # 工具类,请求封装,兼容图片上传的接口 | |-- handle_reponse.py # 工具类,响应结果处理 | |-- handle_attribute.py # 工具类,动态参数设置成类属性 | |-- handle_space.py # 工具类,空格处理工具 | |-- handle_logs.py # 工具类,配置和获取日志记录器 |-- main.py # 框架执行入口
conf配置文件层
#setting.py 用于存放当前框架中的所有配置信息(使用不频繁,修改不频繁的数据) #通过配置文件传参-软编码;在代码中写死-硬编码; #管理者登陆账号信息 user_info = {"user_name":"xdfjewg","password":"dfergbu"} #图片上传信息 image_info = {"file_name":"song.png","file_type":"image/png"} #连接数据库信息 mysql_info = {"host":"11.111.111.11","post":"3306","user":"lemon","password":"vdnjkvs","db":"fsegjvskjv"} #数据库断言替换信息,如"file_path":"2022/04/cd54sdgruissafmscsd/vds"图片上传路径 assert_db_info = {} #前置sql语句执行需要替换的参数 setup_sql_info = {}
logs日志管理层
tools工具层
- excel数据读取
- 数据库操作:连接数据库并查询
- 构造测试数据
- 响应数据提取
- 参数替换
- 存放路径
- 请求封装
- 响应结果处理
- 空格换行符处理工具
- 结果断言
- 装饰器
handle_excel测试数据操作封装
excel中测试数据操作逻辑:通过第三方库openpyxl模块来操作excel中测试数据的读写,打开workbook工作薄,获取sheet表单,获取表单中所有数据,从而调用测试数据,实现数据驱动;自己二次封装一个读写excel中测试数据的工具;
from openpyxl import load_workbook class HandleExcel: def __init__(self,file_name): #file_name: 测试用例文件名称(绝对路径) self.wb_obj = load_workbook(filename=file_name) #load_workbook加载excel #获取所有excel中sheet的名称 self.sheet_names = self.wb_obj.sheetnames def get_excel_test_cases(self,sheet_name): #sheet_name: excel中sheet名称 #临时变量存放数据 cases_list = [] #获取指定表单对象 sheet_obj = self.wb_obj[sheet_name] #iter_rows迭代所有行数据,按行读取封装成list输出结果类似于[('id','title'),(1,'登陆成功')],values_only是False返回对象,True返回单元格对应数据,datas获取表单sheet中所有数据 datas = list(sheet_obj.iter_rows(values_only=True)) #获取表头 case_title = datas[0] #获取表数据 case_datas = datas[1:] #遍历每一行数据 for case in case_datas: #zip(key,val)把两个元素压缩在一起,再把每一行数据使用dict转换成字典类型,zip函数是python的内置函数 result = dict(zip(case_title,case)) cases_list.append(result) self.close_file() return cases_list def close_file(self): #关闭excel self.wb_obj.close() if __name__ == '__main__': #存放excel测试数据的文件名(放本目录下的路径) cl = HandleExcel(file_name="case_data.xlsx",sheet_name="login") cl.get_excel_test_cases()
handle_db数据库操作
import pymysql #从配置文件中导入数据库登陆信息 from conf.setting import mysql_info # **mysql_info class HandleDb: def __init__(self,mysql_info): self.db = pymysql.connect( host=mysql_info["host"], port=mysql_info["port"], user=mysql_info["user"], password=mysql_info["password"], db=mysql_info["db"], #设置自动提交 autocommit=True, #DictCursor设置成字典类型,字典嵌套在list中,一行数据为一个字典; cursorclass=pymysql.cursors.DictCursor ) #游标连接 self.cur = self.db.cursor() #关闭数据库 def db_close(self): #先关游标 self.cur.close() #再关数据库连接 self.db.close() #获取数据,sql查询到什么数据,我们就返回 def get_datas(self,sql): value_list = [] #执行sql语句 self.cur.execute(sql) #获取sql查询的数据 result = self.cur.fetchall() for __dict in result: for val in __dict.values(): value_list.append(val) return value_list # 返回dict类型的数据 def get_data_dict(self,sql): self.cur.execute(sql) result = self.cur.fetchall() return result mysql = HandleDb(mysql_info=mysql_info) if __name__ == '__main__': cl = HandleDb(mysql_info=mysql_info) cl.get_data_dict(sql=sql1)
handle_phone构造测试数据
from faker import Faker from tools.handle_db import mysql class HandlePhone: def __init__(self): self.fk = Faker(locale="zh-cn") def __check_phone(self,phone): sql = "SELECT * FROM 表名 WHERE user_mobile = '{}'".format(phone) #去数据库查询是否注册,注册了再重新生成,直到在数据库中找不到,就表示未注册; result = mysql.get_datas(sql=sql) return result def get_phone(self): while True: #生成新的手机号 phone = self.fk.phone_number() # 去数据库校验是否已注册 result = self.__check_phone(phone=phone) if len(result)>0: #手机号已存在,需要重新生成手机号 continue else: return phone
handel_extract响应数据提取
""" 逻辑: 1、在excel中新增extract_data,用于存储提取数据的key以及提取表达式(jsonpath) 2、在请求需要鉴权的接口之前,去请求登陆接口,读取extract_data中的数据,获取字典的key(响应结果中key),values(jsonpath表达式),从响应结果中提取到鉴权信息,设置到类属性作为全局变量 3、如果是鉴权,就在请求需要鉴权的接口之前,将这个鉴权的token设置到请求头里面 4、如果是参数依赖,其他接口在发请求之前,去获取到相应的参数,替换自己的请求参数 """ import ast from jsonpath import jsonpath from tools.handle_attribute import HandleAttr class HandleExtract: # excel中extract_data字段的数据,response接口响应结果 def handle_extract(self,extract_data,response): if extract_data: extract_data = extract_data if isinstance(extract_data,dict) else ast.literal_eval(extract_data) for key,value in extract_data.items(): token = jsonpath(response,value)[0] #将提取出的token鉴权设置为全局变量 setattr(HandleAttr,key,token) else: print("extract_data数据为空,不需要提取全局变量") if __name__ == '__main__': cl = HandleExtract() #"$..access_token"是jsonpath提取表达式,选择所有名为access_token的属性 extract_data = {"access_token":"$..access_token"} response={'access_token': 'aef4b927-ffa0-4c0c-a8a1-a844c9a7a423', 'token_type': 'bearer', 'refresh_token': '58edfd15-1f90-49f0-9094-7ad74eb72620', 'expires_in': 1295999} cl.handle_extract(extract_data,response)
handle_replace参数替换
def replace_data调用方法及其顺序如下所示:
- def __handel_str 删除换行符和空格
- def __get_replace_keys 获取需要替换的参数名称
- def __set_attribute,其中__set_attribute调用方法及其顺序如下:
3-1 def replace_sql,其中replace_sq调用方法及其顺