• 基于Requests + Pytest + Yaml + Allure 实现Http协议接口自动化


    Github地址 https://github.com/lixiaofeng1993/pytestProject

    灵感来源

      GitHub上的 pytestDemo 和 HttpRunner

    目录结构

    base ==>>  requests请求,返回结果类,测试数据对象化封装

    config ==>> 域名,固定变量,数据库链接

    public ==>> 测试数据处理,全局变量替换,log,自定义异常类等公共方法

    testcase ==>> 测试用例

    data.yml ==>> 测试数据

    parametrize_query.csv ==>> 参数化数据

    用例设计

    1.**局限于pytest参数化形式 `@pytest.mark.parametrize` ,每个测试用例只能对应一个参数化文件**

    2.保证测试用例py文件的简洁,每个用例格式基本固定,代码量少

    3.统一的YAML文件格式

    4.参数化引用csv文件

    用例基本格式

    import pytest
    from public.send_request import SendRequest # 处理发送请求
    from public.log import logger
    from public.sql_to_data import SqlToData # 处理测试数据
    from public.help import get_data_path, os, fun_name, report_setting, report_step_setting, allure
    
    data_path = get_data_path(os.path.dirname(__file__)) # 返回当前 py 文件的绝对路径
    test_params = SqlToData().yaml_db_query(data_path) # 返回对象化的测试数据
    
    
    @allure.severity(allure.severity_level.TRIVIAL) # 测试类等级
    @allure.epic(test_params.get("epic")) # allure报告一级目录
    @allure.feature(test_params.get("feature")) # allure报告二级目录
    class TestUsersCase:
    
        def setup_class(self):
            self.extract = {} # 全局变量
       
        
       # 参数化用例格式 @pytest.mark.parametrize(
    "data", test_params["test_register_user_case"].parametrize) # pytest参数化装饰器 def test_register_user_case(self, data): logger.info("*************** 开始执行用例 ***************") # 获取执行用例函数名 name = fun_name() # 报告展示的测试步骤 report_step_setting(test_params[name]) test_params[name].parametrize = data result, self.extract = SendRequest(test_params[name], self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_params[name]) logger.info("*************** 结束执行用例 ***************\n")
    # 有依赖的参数化用例格式
       @pytest.mark.parametrize(
    "data", test_params["test_one_user_case"].parametrize) def test_one_user_case(self, data): logger.info("*************** 开始执行用例 ***************") # 获取执行用例函数名 name = fun_name() # 报告展示的测试步骤 report_step_setting(test_params[name].case_step_1) # 登录接口 result, self.extract = SendRequest(test_params[name].case_step_1, self.extract).send_request() report_step_setting(test_params[name]) test_params[name].parametrize = data result, self.extract = SendRequest(test_params[name], self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_params[name]) logger.info("*************** 结束执行用例 ***************\n")   
      
       # 非参数化用例格式
    def test_all_user_case(self, test_data): logger.info("*************** 开始执行用例 ***************") # 报告展示的测试步骤 report_step_setting(test_data.case_step_1) # 登录接口 result, self.extract = SendRequest(test_data.case_step_1, self.extract).send_request() report_step_setting(test_data.case_step_2) result, self.extract = SendRequest(test_data.case_step_2, self.extract).send_request() # 报告上展示的测试标题等 report_setting(test_data.case_step_2) logger.info("*************** 结束执行用例 ***************\n")

    单接口 YAML 文件参数化

    test_register_user_case:
      path: /register
      method: post
      headers:
      validate: &validate
        - [ comparator: equal, check: msg, expect: "恭喜,注册成功!", jsonpath: "$.msg" ]
      validate_username: &validate_username
        - [ comparator: equal, check: msg, expect: "用户名/密码/手机号不能为空,请检查!!!", jsonpath: "$.msg" ]
      validate_username_exit: &validate_username_exit
        - [ comparator: contains, check: msg, expect: "用户名已存在", jsonpath: "$.msg" ]
      validate_phone: &validate_phone
        - [ comparator: contains, check: msg, expect: "手机号格式不正确", jsonpath: "$.msg" ]
      validate_sex: &validate_sex
        - [ comparator: contains, check: msg, expect: "输入的性别只能是 0(男) 或 1(女)", jsonpath: "$.msg" ]
      validate_phone_exit: &validate_phone_exit
        - [ comparator: contains, check: msg, expect: "手机号已被注册", jsonpath: "$.msg" ]
      parametrize:
        - [ username: __name, password: "123456", sex: "__random_int(0, 1)",  telephone: __phone, address: __address, validate: *validate ]
        - [ username: "", password: "123456", sex: "__random_int(0, 1)",  telephone: __phone, address: __address, validate: *validate_username ]
        - [ username: sql_one_user, password: "123456", sex: "__random_int(0, 1)",  telephone: __phone, address: __address, validate: *validate_username_exit ]
        - [ username: __name, password: "123456", sex: "__random_int(0, 1)",  telephone: __random_int, address: __address, validate: *validate_phone ]
        - [ username: __name, password: "123456", sex: "__random_int(2, 9)",  telephone: __phone, address: __address, validate: *validate_sex ]
        - [ username: __name, password: "123456", sex: "__random_int(0, 1)",  telephone: sql_one_phone, address: __address, validate: *validate_phone_exit ]
      params:
      upload:
      extract:
      story: 用例-注册接口
      title: 注册接口
      step: 注册接口测试
      description: 该用例是针对 注册接口 的测试
    sql:
      sql_one_user: SELECT u.username from `user` u LIMIT 1
      sql_one_phone: SELECT u.telephone from `user` u LIMIT 1
    epic: 用户数据测试
    feature: 测试Demo

    单接口 CSV 文件参数化

    test_register_user_case:
      path: /register
      method: post
      headers:
      parametrize: ${parametrize_register.csv}
      params:
      upload:
      extract:
      story: 用例-注册接口
      title: 注册接口
      step: 注册接口测试
      description: 该用例是针对 注册接口 的测试
    sql:
      sql_one_user: SELECT u.username from `user` u LIMIT 1
      sql_one_phone: SELECT u.telephone from `user` u LIMIT 1
    epic: 用户数据测试
    feature: 测试Demo

    parametrize_register.csv文件数据

    case_name,username,password,sex,telephone,address,,msg,code
    注册成功,__name,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""equal"",""expect"": ""恭喜,注册成功!"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""0"",""jsonpath"":""""}"
    用户名/密码/手机号不能为空,,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""equal"",""expect"": ""用户名/密码/手机号不能为空,请检查!!!"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2001"",""jsonpath"":""""}"
    用户名已存在,sql_one_user,123456,"__random_int(0, 1)",__phone, __address,,"{""comparator"": ""contains"",""expect"": ""用户名已存在"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2002"",""jsonpath"":""""}"
    手机号格式不正确,__name,123456,"__random_int(0, 1)",__random_int, __address,,"{""comparator"": ""contains"",""expect"": ""手机号格式不正确"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2004"",""jsonpath"":""""}"
    输入的性别格式错误,__name,123456,"__random_int(2, 9)",__phone, __address,,"{""comparator"": ""contains"",""expect"": ""输入的性别只能是 0(男) 或 1(女)"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2003"",""jsonpath"":""""}"
    手机号已被注册,__name,123456,"__random_int(0, 1)",sql_one_phone, __address,,"{""comparator"": ""contains"",""expect"": ""手机号已被注册"",""jsonpath"":""""}","{""comparator"": ""equal"",""expect"": ""2005"",""jsonpath"":""""}"

    有依赖的接口参数化

    test_one_user_case:
      case_step_1:
        path: /login
        method: post
        headers:
        parametrize:
        params:
        json:
          username: sql_one_user
          password: "123456"
        upload:
        extract:
          token: $.login_info.token
          username: $.login_info.username
        validate:
          - [ comparator: equal, check: msg, expect: "恭喜,登录成功!", jsonpath: "$.msg" ]
          - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
        story: 用例-登录接口
        title: 登录接口
        step: 登录接口测试
        description: 该用例是针对 登录接口 的测试
      path: /get/user
      method: get
      headers:
        token: $token
        username: $username
      validate: &validate
        - [ comparator: equal, check: msg, expect: "查询成功", jsonpath: "$.msg" ]
        - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
      validate_username: &validate_username
        - [ comparator: equal, check: msg, expect: "查不到相关用户的信息", jsonpath: "$.msg" ]
        - [ comparator: equal, check: code, expect: 1004, jsonpath: "$.code" ]
      parametrize:
        - [ username: sql_one_user, validate: *validate ]
        - [ username: __name, validate: *validate_username ]
      params:
      upload:
      extract:
      story: 用例-查询指定用户信息接口
      title: 查询指定用户信息接口
      step: 查询指定用户信息接口测试
      description: 该用例是针对 查询指定用户信息接口 的测试
    sql:
      sql_one_user: SELECT u.username from `user` u LIMIT 1
    epic: 用户数据测试
    feature: 测试Demo

     非参数化接口,存在依赖

    test_all_user_case:
      case_step_1:
        path: /login
        method: post
        headers:
        parametrize:
        params:
        json:
          username: sql_one_user
          password: "123456"
        upload:
        extract:
          token: $.login_info.token
          username: $.login_info.username
        validate:
          - [ comparator: equal, check: msg, expect: "恭喜,登录成功!", jsonpath: "$.msg" ]
          - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
        story: 用例-登录接口
        title: 登录接口
        step: 登录接口测试
        description: 该用例是针对 登录接口 的测试
      case_step_2:
        path: /users
        method: get
        headers:
          token: $token
          username: $username
        parametrize:
        params:
        json:
        upload:
        validate:
          - [ comparator: equal, check: msg, expect: "查询成功", jsonpath: "$.msg" ]
          - [ comparator: equal, check: code, expect: 0, jsonpath: "$.code" ]
        story: 用例-查询所有用户信息接口
        title: 查询所有用户信息接口
        step: 查询所有用户信息接口
        description: 该用例是针对 查询所有用户信息接口 的测试
    sql:
      sql_one_user: SELECT u.username from `user` u LIMIT 1
    epic: 用户数据测试
    feature: 测试Demo

    测试数据对象化封装

    def object_data(test_data: dict, file_path: str, case_step_num=10):
        """
        封装测试数据为对象
        :param test_data: 测试数据
        :param file_path: 测试数据文件路径
        :param case_step_num: 测试用例依赖接口数量
        :return: 字典包含的数据对象
        """
        obj = dict()
        case_step_list = list()
        case_step_num = int(case_step_num) if str(case_step_num).isdigit() else 10
        case_step_num = 10 if case_step_num < 10 else case_step_num
        for i in range(1, case_step_num + 1):
            case_step_list.append(f"case_step_{i}")
        for keys, values in test_data.items():
            obj[keys] = ObjectData()
            if isinstance(values, dict):
                for key, value in values.items():
                    setattr(obj[keys], key, value)
                    setattr(obj[keys], "file_path", file_path)
                    if isinstance(value, dict):
                        step = CaseStep()
                        for k, v in value.items():
                            setattr(step, k, v)
                            setattr(step, "file_path", file_path)
                        if key in case_step_list:
                            setattr(obj[keys], key, step)
        obj.update({
            "epic": test_data.get("epic"),
            "feature": test_data.get("feature")
        })
        return obj

    allure报告

    愿你走出半生,归来仍是少年!
  • 相关阅读:
    彻底理解cookie,session,token.md
    13Gin中使用jwt
    qstat f队列状态是au和s,如何恢复
    shell三剑客
    mysql根据俩个字段之差进行区间分组
    运行jnlp文件
    mount o后面参数含义
    Linux常用命令
    java VelocityEngine 属性key值得组成说明
    Linux 基本命令 sed
  • 原文地址:https://www.cnblogs.com/changqing8023/p/15608857.html
Copyright © 2020-2023  润新知