pytest参数化-读取excel+allure报告展示

由于近期公司要求项目接口自动化且使用参数化、装饰器等,我在网上查了一下资料,现在整理下,放便以后代码套用

版本:

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下

                 pytest参数化-读取excel+allure报告展示

 

 

 

3、控制台验证环境变量是否配置成功

pytest参数化-读取excel+allure报告展示

 

 

看下我的代码结构

pytest参数化-读取excel+allure报告展示

 

 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参数化-读取excel+allure报告展示

 

 

 

三、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中指定字段的值

 

上一篇:Pytest之重运行及生成测试报告


下一篇:pytest与allure结合生成测试报告(mac系统)