当前位置: 首页 > article >正文

pytest+request+yaml+allure搭建低编码调试门槛的接口自动化框架

接口自动化非常简单,大致分为以下几步:

  1. 准备入参
  2. 调用接口
  3. 拿到2中response,继续组装入参,调用下一个接口
  4. 重复步骤3
  5. 校验结果是否符合预期

一个优秀接口自动化框架的特点:

  1. 【编码门槛低】,又【能让新手学到技术】
  2. 【低调试门槛】
  3. 【优秀的可读性】
  4. 【很好的可维护性】
  5. 能很【方便多人协同】,以便【自动化代码能不断积累】形成规模效应,且【自动化case覆盖率可量化】
  6. 有失败重试机制,克服环境的不稳定
  7. 【清晰的测试报告】
  8. 能【低成本且灵活地触发】(指定环境、指定时间、指定范围)

综上,我们需要搭建一个同时满足以上特点的轻量级接口自动化框架

首先,管理接口

    达成:【编码门槛低】【方便多人协同】【优秀的可读性】【很好的可维护性】

  • 被测接口单独管理,目录结构和和开发使用的yapi保持一致,统一放置在仓库下,“接口管理”目录中;如下:

        

  • 每个接口使用yaml文件管理,如下,且使用中文命名增加可读性。
  • method: post
    host: "${host}"
    url: /copy/trading/v1/follower/edit
    headers:
      {token: "799b72f3f94973f5a54a54204eac96a1aa94cd5d365245929098e921b2ddc154"}
    body:
      {
        "followType": 1,
        "traderUid": 102806,
        "margin": "100",
        "followAmount": "1000",
        "stopLossRation": null,
        "positionMode": 1,
        "positionModeConfig": 0,
        "leverModeConfig": 0,
        "leverage": 1,
        "marginModeConfig": 0,
        "marginMode": 0,
        "positionRisk": null,
        "slippage": "0.006",
      }
    response:
      code: # 响应码(0: 成功)
      msg: #  返回描述(code为0时,返回:成功)
      data: # 响应数据
    
    
    
  • 调用接口时,默认用yaml文件中的参数(剔除response,response只做示例用),如需替换,会在调用过程中,标记具体需要替换的key value即可,如下,方便快捷
  • r.invoke_api("更新用户配置.yaml", 
                     headers={"token": 'trader_son_token'}, 
                     body={"positionMode": 1})

其次,编写代码,调用接口

        达成:【编码门槛低】【优秀的可读性】【低调试门槛】【清晰的测试报告】

  • 封装requests包,使用requests.session(),根据上述接口文档,调用get或post方法,且使用allure将入参和返回都放置到测试报告里面,且此块代码无需实际接口测试同学关注。【能让新手学到技术】
  • class RequestUtil:
        def invoke_api(self, api, headers=None, body=None):
            """
            解析需要调用的接口,并且进行参数替换,基础断言等
            :param api: 需要调用的接口
            :param header: 需要传入的请求头
            :param body: 需要传入的请求体
            :return: 返回接口响应的结果
            """
            api_name = api
            param = YamlUtil.parse_api(api_name)
            if headers is not None:
                for key in headers:
                    param['headers'][key] = headers[key]
            if body is not None:
                for key in body:
                    param['body'][key] = body[key]
            params = self.replace_value(param)
            with allure.step(f"步骤:{api_name}"):
                response = self.send_request(params)
                if self.run_mode == 1:
                    print("")
                    print(f"调用接口:{api_name}")
                    print(params)
                    print(response)
                    return response
                else:
                    return response
    
        def send_request(self, *args):
            """
            发起接口请求
            :param args: 接口请求的参数
            :return: 接口返回的数据
            """
            method = args[0]['method'].lower()
            ip = self.replace_value(args[0]['host'])
            url = ip + args[0]['url']
            headers = args[0]['headers']
            body = args[0]['body']
            data = body
            if method == "get":
                res = self.sess.get(url, params=data, headers=headers)
            elif method == "post":
                if 'files' in args:
                    files = args[0]['files']
                    file_path = os.path.join(YamlUtil.data_file_path, files)
                    with open(file_path, 'rb') as file:
                        files = {'file': ('image.jpg', file)}
                        res = self.sess.post(url, files=files, headers=headers)
                res = self.sess.post(url, json=data, headers=headers)
            else:
                print("请求方式错误")
            try:
                res = res.json()
                with allure.step(f"URL:{url}"):
                    ...
                with allure.step(f"Method:{method}"):
                    ...
                with allure.step(f"Headers:{json.dumps(headers)}"):
                    ...
                with allure.step(f"Body:{json.dumps(data, ensure_ascii=False)}"):
                    ...
                with allure.step(f"Response:{json.dumps(res, ensure_ascii=False)}"):
                    return res
            except Exception as e:
                print("该接口返回的结果不是json数据?")
                res = res.text
                with allure.step(f"URL:{url}"):
                    ...
                with allure.step(f"Method:{method}"):
                    ...
                with allure.step(f"Headers:{json.dumps(headers)}"):
                    ...
                with allure.step(f"Body:{json.dumps(data, ensure_ascii=False)}"):
                    ...
                with allure.step(f"Response:{json.dumps(res, ensure_ascii=False)}"):
                    return res
  • 通过pytest的fixture装饰器,将RequestUtil注入进去每个测试用例里面去
  • @pytest.fixture(scope="session")
    def r():
        return RequestUtil()
    

  • 测试编码同学只需要使用方法,即可完成接口调用和response获取
  • 接口:百度.yaml
  • method: post
    host: https://ug.baidu.com
    url: /mcp/pc/pcsearch
    headers:
      {}
    body:
      {
        "errno": 0,
        "errmsg": "ok",
        "data": {
          "log_id": "1652589279",
          "action_rule": {
            "pos_1": [],
            "pos_2": [],
            "pos_3": []
          }
        }
      }

    事实上,测试代码只有这么点:【编码门槛低】只需要能看懂接口文档,和http的基本知识,以及知道Python的字典和assert,便可轻松完成编码

    def test_百度(r):#其中 r为fixture装饰器,通过conftest.py注入进去
        resdic= r.invoke_api("百度.yaml",
                                 headers={},
                                 body={})
    
    assert resdic[errmsg] == "ok"
    assert resdic[data][log_id] == "3277317105"
    
    
  • 不管成功失败,测试报告都会打出每一次请求的入参和返回,如下图,都不用在IDE里面调试。【低调试门槛】【清晰的测试报告】
  • 另外,数据驱动的case,用如下的方式编写
  • import pytest
    
    
    @pytest.mark.parametrize('emails,password,assert1', [
        ('11111@qq.com', 'Tset111', 'success'),
        ('22222@qq.com', 'Tset222', 'fail'),
        ('33333@qq.com', 'Tset333', 'error'),
    ])
    def test_注册用户(r, emails, password, assert1):
        response_info = r.invoke_api('注册用户.yaml', body={"emails": emails, "password": password})
        assert response_info['data'] == assert1
    

再次,测试用例的组织和结构

        达成:【方便多人协同】【自动化代码能不断积累】【自动化case覆盖率可量化】

  1. 手工用例遵循实际的业务模块树形结构,做较精细的模块拆分
  2. 末级手工用例使用xmind管理,作为用例树的末级,标题上按如下打标:哪些场景需要被覆盖(目标)?已被覆盖(进度)?功能覆盖率多少(量化)?哪些地方还需要验证前端?一目了然,工时评估更透明
    • 是否需要自动化
    • 是否已被自动化
    • 是否还需要验证前端
  3. 自动化测试用例目录,和手工用例目录保持一致,
  4. 每个自动化case一个.py文件,自动化测试用例名与手工测试名保持一致。如此保障一一对应
  5. 如若涉及一对多:
    1. 如果涉及单接口的,自动化测试用例名与手工用例末级目录一致;
    2. 如果涉及数据驱动的,自动化测试用例名与手工用例末级目录一致;
  6. 通过以上措施,很方便计算出自动化case覆盖率(分子为已完成自动化case,分母为达标为:需要自动化的case数)

再次,通过配置文件,和参数化运行

        达成【低成本且灵活地触发】

  • yaml方式的配置文件
  • run_mode: 2 # 1调试模式/2正式模式
    run_env: one  # 运行脚本环境
    
    test:
      host: http://api.test.xyz
      db:
        host:
        port:
        user:
        password:
      redis:
        host:
        password:
    
    
    testa:
      host: http://api.testa.xyz
      db:
        host:
        port:
        user:
        password:
      redis:
        host:
        password:
    
    # 环境
    one:
      # 接口地址
      host: 
      # b连接信息
      db:
        host:
        port:
        user:
        password:
      # redis连接信息
      redis:
        host:
        password:
    
    
  • main函数运行的时候,拿到具体运行的环境信息,读取对应的配置
  • import os
    import pytest
    from pytest_jsonreport.plugin import JSONReport
    import argparse
    
    from Core.parse_yaml import YamlUtil
    from Core.robot_utils import Robot
    
    
    def main(env):
        YamlUtil.clear_env_yaml()
        if env.env is not None:
            e = env.env
            data = {"env": e}
            YamlUtil.write_env_yaml(data)
    
        current_path = os.path.dirname(os.path.abspath(__file__))
        json_report_path = os.path.join(current_path, 'report/json')
        html_report_path = os.path.join(current_path, 'report/html')
        plugin = JSONReport()
    
        pytest.main(["自动化用例/百度搜索.py", '--alluredir=%s' % json_report_path, '--clean-alluredir'], plugins=[plugin])
    
        # 生成allure报告
        os.system('allure generate %s -o %s --clean' % (json_report_path, html_report_path))
        os.system('allure open %s' % html_report_path)
    
    
    if __name__ == "__main__":
        parser = argparse.ArgumentParser()
        parser.add_argument("--env", help="参数为需要执行的环境")
        args = parser.parse_args()
        main(args)
  • #在testa环境运行
    python run.py --env testa

http://www.kler.cn/a/503155.html

相关文章:

  • 使用 versions-maven-plugin 和 flatten-maven-plugin 插件惯例 maven 项目版本
  • 服务器数据恢复—raid5故障导致上层ORACLE无法启动的数据恢复案例
  • 宝塔面板使用 GoAccess Web 日志分析教程
  • 2024年开发语言热度排名
  • MongoDB如何使用
  • Java聊天小程序
  • 【PGCCC】PostgreSQL 事务及其使用方法
  • 【C++boost::asio网络编程】使用asio协程搭建异步echo服务器的笔记
  • JVM虚拟机的组成 笼统理解 六大部分 类加载子系统 运行时数据区 执行引擎 本地接口 垃圾回收器 线程工具
  • excel实现下拉单选
  • 服务器中常见的流量攻击类型包括哪些?
  • 开源安防软件ClamAV —— 筑梦之路
  • [c语言日寄]c语言也有“回”字的多种写法——整数交换的三种方式
  • Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(3)
  • vscode vue 自动格式化
  • MySQL主从部署(保姆版)
  • 【RabbitMQ】SpringBoot整合RabbitMQ实例
  • C++(类和对象)
  • 生成式数据增强在大语言模型中的应用与实践
  • UE5.4运行报错解决(关于osg使用-无法解决的外部命令)(未解决)
  • 优秀持久层框架——MyBatis
  • 两分钟解决 :![rejected] master -> master (fetch first) , 无法正常push到远端库
  • Chromium 中的 WebUI
  • Springboot内置Apache Tomcat 安全漏洞(CVE-2024-50379)
  • vue2修改表单只提交被修改的数据的字段传给后端接口
  • JavaScript:简介