由于近期公司要求项目接口自动化且使用参数化、装饰器等,我在网上查了一下资料,现在整理下,放便以后代码套用
版本:
pytest==6.2.1
pytest-html ==2.1.1
pyyaml ==5.3.1
requests ==2.24.0
xlrd ==2.0.1
allure-pytest == 2.8.34
allure命令行工具下载及配置
1.下载allure命令行工具
https://github.com/allure-framework/allure2/releases
2、将allure安装包bin目录得路径添加到系统变量path下
3、控制台验证环境变量是否配置成功
看下我的代码结构
saas:根目录
interface-api:项目目录,如果想要多个文件夹下的文件可以互相调用可以在这个项目目录设置,右键--》Mark Directory as --> Source Roots,这样模块间调用 比如:from utils.assert_common import assert_common 就不会报错
api:存放接口,如果接口自动化直接调用Exel 中的测试数据的话,则不需要这个目录
data:存放测试数据
log:存放日志
report:存放测试报告
scripts:存放测试脚本 test_*
utils:存放一些通用的调用代码
api_config.py 文件存放环境信息配置
tox.ini 文件 pytest的运行命令
一、使用xlrd库,封装常用读取xls的操作
模块:XlrdUtils.py
# -*- coding:utf-8 -*- import xlrd class XlrdUtils(object): def __init__(self, filename): self.workbook = xlrd.open_workbook(filename, formatting_info=True) def get_cell_value(self, sheet_index_or_name, row_index, col_index): sheet = self.__get_sheet(sheet_index_or_name) if sheet.nrows and sheet.ncols: return sheet.cell_value(row_index, col_index) else: raise BaseException("Index out of range!") def __get_sheet(self, sheet_index_or_name): """ 根据sheet的索引或名称,获取sheet对象 :param sheet_index_or_name: sheet的索引或名称 :return:sheet对象 """ if isinstance(sheet_index_or_name, int): if len(self.workbook.sheet_names()) > sheet_index_or_name: return self.workbook.sheet_by_index(sheet_index_or_name) else: raise BaseException("Invalid Sheet Index!") elif isinstance(sheet_index_or_name, str): if sheet_index_or_name in self.workbook.sheet_names(): return self.workbook.sheet_by_name(sheet_index_or_name) else: raise BaseException("Invalid Sheet Name!") def get_rows_num(self, sheet_index_or_name): return self.__get_sheet(sheet_index_or_name).nrows def get_cols_num(self, sheet_index_or_name): return self.__get_sheet(sheet_index_or_name).ncols def get_sheet_names(self): return self.workbook.sheet_names() def items_value(self, sheet_index_or_name): """ 打印sheet页的表格数据 :param sheet_index_or_name:sheet的索引或名称 :return: """ sheet = self.__get_sheet(sheet_index_or_name) for row in range(0, sheet.nrows): for col in range(0, sheet.ncols): print(self.get_cell_value(sheet_index_or_name, row, col), end="\t") print() @staticmethod def datatodict(data): """ 从表格获取单元格的数据转化为字典 :return: """ str_data = data.replace("{", "").replace("}", "") data_split = str_data.split(",") dict_data = {} for dat in data_split: k, v = dat.split(':') dict_data[k] = v return dict_data
二、使用上一步封装的XlrdUtils.py模块,继续封装;
2.1 read_test_data函数功能描述:读取xls的数据,返回的值为列表或字典;
如果不指定获取某一sheet页的数据,就会获取所有sheet页的数据,返回的值为字典,其中字典的key为sheet页的名称或索引值,字典的value为一个列表,并且这个列表中每一个元素为sheet页表格中每一行数据组成的子列表;
如果指定获取某一sheet页的数据,则返回的值为列表;
并且满足定制化需求,可以读取全部的数据,也可只读取某一个sheet页的数据,也可只读取某个sheet页的某一行数据,并且满足只返回sheet页的某几列数据
2.2 RD_By类的说明:通过2.1的介绍,sheet页的每一行数据为一个子列表,当测试用例的参数需要使用子列表中具体的某一个值时,可以通过RD_By.xxx的方式提供索引,以方便获取数据
模块:ReadTabDataUtils.py
import json from utils.XlrdUtils import XlrdUtils # 满足定制化需求 def read_test_data(xl_path, sheet_index_or_name=None, row_num=None, *cols_index): """ :param xl_path: xls文件路径 :param sheet_index_or_name: sheet页的索引或名称 :param row_num: 获取sheet表格某一行的数据 :param cols_index: 获取sheet表格某一列或几列的数据 :return: 返回字典或列表 """ xlUtils = XlrdUtils(xl_path) xl_dict = {} cell_list = [] if sheet_index_or_name is not None and row_num is not None: cell_list.append(__get_cell_value(xlUtils, sheet_index_or_name, row_num, *cols_index)) elif sheet_index_or_name is not None and row_num is None: for row_num in range(1, xlUtils.get_rows_num(sheet_index_or_name)): cell_list.append(__get_cell_value(xlUtils, sheet_index_or_name, row_num, *cols_index)) elif sheet_index_or_name is None and row_num is not None: for sheet_index_or_name in range(0,len(xlUtils.get_sheet_names())): cell_list = [] cell_list.append(__get_cell_value(xlUtils, sheet_index_or_name, row_num, *cols_index)) xl_dict[sheet_index_or_name] = cell_list elif sheet_index_or_name is None and row_num is None: for sheet_index_or_name in range(0, len(xlUtils.get_sheet_names())): cell_list = [] for row_num in range(1, xlUtils.get_rows_num(sheet_index_or_name)): cell_list.append(__get_cell_value(xlUtils, sheet_index_or_name, row_num, *cols_index)) if len(cell_list) != 0: xl_dict[sheet_index_or_name] = cell_list if not xl_dict: if len(cell_list) != 0: xl_dict[sheet_index_or_name] = cell_list if sheet_index_or_name != None: return xl_dict[sheet_index_or_name] else: return xl_dict def trans_data(dict_data:dict): payload = {} for K, V in dict_data.items(): if type(V) is dict: V = json.dumps(V, ensure_ascii=True) payload[K] = V return payload def __get_cell_value(xlUtils:XlrdUtils, sheet_index_or_name, row_num,*cols_index): if len(cols_index) == 0: case_num = xlUtils.get_cell_value(sheet_index_or_name, row_num, 0) moudle_name = xlUtils.get_cell_value(sheet_index_or_name, row_num, 1) case_name = xlUtils.get_cell_value(sheet_index_or_name, row_num, 2) request_method = xlUtils.get_cell_value(sheet_index_or_name, row_num, 3) request_url = xlUtils.get_cell_value(sheet_index_or_name, row_num, 4) request_headers = json.loads(xlUtils.get_cell_value(sheet_index_or_name, row_num, 5)) params_desc = xlUtils.get_cell_value(sheet_index_or_name, row_num, 6) request_data = json.loads(xlUtils.get_cell_value(sheet_index_or_name, row_num, 7)) except_result_desc = xlUtils.get_cell_value(sheet_index_or_name, row_num, 8) error_message = json.loads(xlUtils.get_cell_value(sheet_index_or_name, row_num, 9)) return [case_num, moudle_name, case_name, request_method, request_url, trans_data(request_headers), params_desc, trans_data(request_data), except_result_desc, trans_data(error_message)] else: row_data = [] for cell_cols_index in cols_index: row_data.append(xlUtils.get_cell_value(sheet_index_or_name, row_num, cell_cols_index)) return row_data class RD_By(object): CASE_NUM = 0 MOUDLE_NAME = 1 CASE_NAME = 2 REQUEST_METHOD = 3 REQUEST_URL = 4 REQUEST_HEADERS = 5 PARAMS_DESC = 6 REQUEST_DATA = 7 EXCEPT_RESULT_DESC = 8 ERROR_MESSAGE = 9
测试用例模板:
三、pytest参数化读取-xls
模块:
test_gateway_management.py
# 网络管理测试脚本 import logging, allure, pytest from utils.assert_common import assert_common from utils.management_login_common import management_login_common import os,sys from utils.ReadTabDataUtils import read_test_data from utils.request_common import request_common @allure.feature("网络管理模块") class TestGatewayManagement: """网络管理测试用例""" @allure.title("登录") @allure.description("登录-获取token") def test_gateway_management(self): # 登录管理端 management_login_common(self) curPath = os.path.abspath(os.path.dirname(__file__)) rootPath = os.path.split(curPath)[0] sys.path.append(rootPath) data_path = rootPath + r"/data/gateway_data.xls" ''' @allure.severity装饰器按严重性级别来标记case 执行指定测试用例 --allure-severities blocker BLOCKER = 'blocker' 阻塞缺陷 CRITICAL = 'critical' 严重缺陷 NORMAL = 'normal' 一般缺陷 MINOR = 'minor' 次要缺陷 TRIVIAL = 'trivial' 轻微缺陷 ''' @allure.story("网络管理模块测试") @allure.title(" ") @allure.severity("normal") @allure.description("网络管理模块接口测试") @pytest.mark.parametrize("moudle_name,case_name,request_method,request_url,req_data,exc_data", read_test_data(data_path, "gateway", None, 1, 2, 3, 4, 7, 9)) def test_gateway_management_case(self, moudle_name, case_name, request_method, request_url, req_data, exc_data): res_gateway_managent_text = request_common(moudle_name=moudle_name, request_method=request_method, url=request_url, data=eval(req_data)) logging.info("用例名称:{}的结果:{}".format(case_name, res_gateway_managent_text.json())) assert_common(res_gateway_managent_text, message=exc_data)
@pytest.mark.parametrize(argnames, argvalues)
参数:
argnames:以逗号分隔的字符串
argvaluse: 参数值列表,若有多个参数,一组参数以元组形式存在,包含多组参数的所有参数
以元组列表形式存在
案例中,直接调用read_test_data方法获取excel中指定字段的值