基于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报告

愿你走出半生,归来仍是少年!
原文地址:https://www.cnblogs.com/changqing8023/p/15608857.html