基于pytest自动化测试工具开发

前言

本工具功能:对比不同系统间相同业务处理上差异,输出对比结果报告

自动化测试工具的开发需结合好实际的测试场景,与测试人员沟通好测试时各种测试场景下具体测试操作流程:要准备哪些数据,要操作哪些服务器,测试执行与结果查看方式(ui, linux命令,app, 接口),最好是工具开发人员自己熟悉测试业务并能执行对应的手工测试,这样开发的工具使用上才不会偏离实际测试使用的场景。围绕工具开发还需要做测试案例模板制定(预制条件,操作步骤,预期结果),测试结果呈现(用例执行结果输出格式,测试数据统计格式)。

一. 分层,分阶段,数据分离设计

分层(程序分层):

公共库 | 系统操作库 | 业务脚本 | 调度程序

分阶段(数据处理分阶段):

分阶段,可配置 ,阶段点结果或中间数据可输出可存储, 数据与计算分离

二. 设计风格

统一风格:目录名,文件名,类名,方法名,变量命名规则统计;配置风格统一

可配置:

1.易于维护:结合应用情况将需要的参数配置化,减少因业务场景或环境等变动而改动程序

2.解放开发人力:配置化后使用人员可根据环境/业务变动来更改配置,部分场景无需开发介入

易裁剪:方便根据测试需求对测试步骤增减(测试步骤模块化),可配置化

三. 框架基本功能

1.日志记录 -- logger,装饰器

2.报告生成,数据统计:执行时间,执行范围,失败成功率,pytest-html; allure

3,失败重执行

4.可选择性执行case

5.方便调试打印的模式

6.超时限制,异常告警 func-timeout

7.邮件功能

8.数据缓存:

*提供case依赖数据缓存防止环境故障影响依赖数据获取

*历史结果数据缓存与识别减少通过的case重复执行

9.环境检查功能:检查环境部署,服务器连接等

10.个性化测试报告功能(图表统计用例数据,报告数据,对比数据)

11.版本控制:

     1.gitlab代码库的版本管理 -- 大量业务逻辑变更

     2.同一版本的代码对应多个场景的版本管理 -- 少量逻辑或配置变更

四. 编程实现

4.1 统一入口程序,命令行实现脚本调度

使用argparse库进行命令行定义,不同的组合调度到不同的功能脚本

    def get_args(self):
        """
        输入参数定义
        :return:
        """
        parser = argparse.ArgumentParser()
        parser.add_argument("-p", "--plugin", help="run the plugin case")
        parser.add_argument("-t", "--type", default='business',
                            help="run case type: access|business|precheck")
        parser.add_argument("-c", "--compare", help="result|database")
        parser.add_argument("-f", "--file", help="run target file path")
        parser.add_argument("--report",
            help="html|allure,output html report and file path from config.yml/html_report;cover argument --pytest-args")
        # parser.add_argument(
        #     "--pytest-args", help="pytest args: example  -v -s -q --html=./report/report.html ")
        parser.add_argument("--pytest-args", help="pytest args: example  '-v -s -q --html=report.html' ")
        parser.add_argument("-rr","--result_report", help="result | database; Report for statistics compare result data ")
        parser.add_argument("--run_all_force",help="run all case don't copy last file and compare record file",default=False,action='store_true')
        parser.add_argument("--casefile",help="testcase filename, compare: filename1|filename2")
        parser.add_argument("--sheetname", help="testcase sheetname")
        args = parser.parse_args()
        self._args = args
        return args

4.2利用pytest的自定义命令参数

需求:工具中需要根据控制程序传入的参数进行脚本逻辑的判断,使用pytest自定义参数方法

def pytest_addoption(parser):
    """
    添加自定义参数
    cmd* 命名防止与pytest原参数冲突
    """
    parser.addoption("--cmdplugin",help="同run.py plugin参数")
    parser.addoption("--cmdtype",help="同run.py type参数")
    parser.addoption("--cmdcompare", help="同run.py compare参数")
    parser.addoption("--cmdrunall", help="同run.py run_all_force参数")

@pytest.fixture(scope="session")
def cmdopt(pytestconfig):
    """
    自定义参数获取
    """
    cmdopts = {}
    cmdopts['cmdplugin'] = pytestconfig.getoption('--cmdplugin')
    cmdopts['cmdtype'] = pytestconfig.getoption('--cmdtype')
    cmdopts['cmdcompare'] = pytestconfig.getoption('--cmdcompare')
    cmdopts['cmdrunall'] = pytestconfig.getoption('--cmdrunall')
    return cmdopts

4.3合理使用pytest fixture初始化执行全局变量,业务全局变量,前置后置操作:客户登录/退出等

需求:多个脚本使用相同的变量或数据

@pytest.fixture(scope='session')
def run_variables(cmdopt):
    """
    初始化脚本运行全局参数
    """
    cmdopts = cmdopt
    plugin = cmdopts['cmdplugin']
    if str(plugin) == str(None):
        client_module = None
        handler_module = None
        plugin_config = None
    else:
        client_file = "compare.plugin.{}.src.client".format(plugin)
        client_module = importlib.import_module(client_file)
        handler_file = "compare.plugin.{}.src.handler".format(plugin)
        handler_module = importlib.import_module(handler_file)
        ypath = os.path.join(COMPARE_PATH, 'plugin/{}/conf/conf.yml'.format(plugin))
        plugin_config = YamlHelper(ypath).data
    all_variables = {}
    all_variables['cmdopts'] = cmdopts #命令参数字典
    all_variables['client_module'] = client_module #客户端模块
    all_variables['handler_module'] = handler_module #客户端操作模块
    all_variables['plugin_config'] = plugin_config #插件配置
    all_variables['current_result'] = {}  #当前执行用例行结果
    all_variables['run_statistics'] = {'pass': 0, 'fail': 0, 'norun': 0}  #当前执行用例数据统计
    return all_variables

4.4充分利用pytest钩子函数

合理的利用pytest钩子函数能解决程序封装中碰到的很多问题,并能实现自己想要的各种功能效果

4.5合理的参数化

用例参数化,命令行参数化,配置文件参数化

4.6配置化数据处理方法

提供数据处理函数列表供用户自己配置需要的函数对需要的数据进行处理

  msg:
    table: 'HS_ASSET.CBPREALTIME'
    compare_column: ["BRANCH_NO","FUND_ACCOUNT","CLIENT_ID","EXCHANGE_TYPE","STOCK_ACCOUNT","STOCK_CODE","ENTRUST_BS","ENTRUST_PROP","EXPIRE_YEAR_RATE","PRETERM_YEAR_RATE","ENTRUST_PRICE","ENTRUST_AMOUNT","ENTRUST_BALANCE","BUSINESS_AMOUNT","BUSINESS_PRICE","BUSINESS_BALANCE","SEAT_NO","PROP_STOCK_ACCOUNT","PROP_SEAT_NO","PROP_BRANCH_NO","REAL_TYPE","REAL_STATUS"]
    compare_rule: {'BRANCH_NO':{'func':'func_test','param':[]}}
    not_show: {'cancel_cl_ord_id':''}
    file1:
      build_sql:
        select_key: {"cust_id":"CLIENT_ID","cl_ord_no":"ENTRUST_NO","security_id":"STOCK_CODE"}
        order_by: 'CURR_TIME'
        add_where: 'and ENTRUST_NO > 0'
      convert:
        report_col: {"cl_ord_no":{'func':"func_strend",'params':[-8]}}
        db_col: {}

    file2:
      build_sql:
        select_key: {"cust_id":"CLIENT_ID","cl_ord_no":"ENTRUST_NO","security_id":"STOCK_CODE"}
        order_by: 'CURR_TIME'
        add_where: ''
      convert:
        report_col: {"cl_ord_no":{'func':"func_strend",'params':[-8]}}
        db_col: {}
def compare_column_data(report_data1,report_data2,column_name,row_data1,row_data2,compare_rule):
    """
    数据对比
    report_data1,report_data2: 输入文件行数据
    column_name: 当前要对比的列名
    row_data1,row_data2: 数据库查询到的两组整行数据
    compare_rule:  配置中的对比规则
    return:  True , False
    """
    def func_accuracy(r_data1, r_data2, col_name, accuracy):
        #accuracy: 误差范围
        return True if abs(float(r_data1[col_name]) - float(r_data2[col_name])) < float(accuracy) else False
    
    def func_at_point(r_data1, r_data2, col_name, point):
        #point: 加/减/不操作  三个值上即pass
        if float(r_data1[col_name]) == float(r_data2[col_name]): 
            return True
        elif abs(float(r_data1[col_name]) - float(r_data2[col_name])) == abs(float(point)):
            return True
        else:
            return False


    func = compare_rule.get(column_name, {}).get('func')
    if not func: return row_data1[column_name] == row_data2[column_name]

    con_params = compare_rule.get(column_name, {}).get('params', [])
    con_params = get_inner_params(con_params,report_data1)

    func_params = [row_data1, row_data1, column_name] + con_params
    
    try:
        return eval(func)(*func_params)
    except Exception as e:
        return str(e)

4.7 allure报告生成,程序自定义输出报告/log

pytest -v -q --alluredir={cash_dir}

./allure generate --clean {cash_dir} -o {report_dir}

五.性能优化

缓存使用重复度高的数据,

六.改进

1.充分使用工具,让使用人员按自己角度提需求能收集很多有用的改进点

2.自己理解测试,使用工具测试,发现改进点,开发人员与测试人员角度不一样,提的改进点会不一样有不同的效果。

七.版本控制

     1.gitlab代码库的版本管理;

     2.同一版本的代码对应多个场景的版本管理  -- 少量代码逻辑或参数区分版本可选择此方法

 一句话描述:统一的配置,同一个版本官宦关系函数,规律定义同一业务不同版本的处理方法

      参数版本控制

      函数版本控制

        配置版本与方法映射关系

定义函数版本控制统一处理方法(统一处理方法利于管理与搜索) 

同一业务不同版本方法定义 (_版本后缀)

        

        

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值