首页 > 其他分享 >接口自动化测试

接口自动化测试

时间:2023-10-03 09:33:24浏览次数:37  
标签:__ sheet name get self 接口 测试 自动化 logger

基于pytest和allure构建自动化测试框架与项目

框架目录结构

我们要构建一个自动化测试框架,就要以项目的概念来对项目中的所有代码文件进行划分目录和文件结构,需要设计一个合理的目录结构,以便与测试开发团队的其他人员的开发和测试,也便于项目的维护

设计的项目目录如下

根目录
├── api                  # 封装测试项目的api接口[用于mock测试]
│   └── __init__.py
├── config.py            # 项目代码配置文件
├── data                 # 测试数据/测试用例存放目录
├── libs                 # 第三方lib库
├── main.py              # 项目入口
├── pytest.ini           # pytest模块的配置文件
├── reports              # HTML测试报告生成目录
├── results              # 测试报告生成目录
├── tests                # 测试用例脚本存放目录
├── utils                # 自定义工具类
├── requirments.txt      # 项目依赖模块 
 

 

配置文件 config.py

import pathlib  # 路径操作模块,替代os.path模块,可以通过对象的方式操作路径

# 项目目录的主目录文件路径[字符串]
BASE_DIR_STR = pathlib.Path(__file__).parent.resolve().as_posix()

# 项目目录的主目录路径[路径对象]
BASE_DIR = pathlib.Path(BASE_DIR_STR)

# 项目名字
PROJECT_NAME = "接口自动化测试框架"

# 自动化测试项目的运行IP和端口
HOST = "127.0.0.1"
PORT = 8088

if __name__ == '__main__':
    print(BASE_DIR_STR, type(BASE_DIR_STR))
    print(BASE_DIR, type(BASE_DIR))

 

入口文件main.py

import pytest
import os
import sys
import shutil

import config

if __name__ == '__main__':
    try:
        # 删除之前存在的报告文件夹
        shutil.rmtree("./reports")
        shutil.rmtree("./results")
    except Exception as e:
        # TODO:需要添加日志
        print(e)

    # 启动 pytest 测试框架,启动参数配置在 pytest.ini 中
    pytest.main()

    # 生成测试报告
    os.system(f"{config.BASE_DIR.joinpath('libs/allure/bin/allure')} generate  ./results -o ./reports")

    # 打开测试报告
    os.system(f"{config.BASE_DIR.joinpath('libs/allure/bin/allure')} serve ./results  -h {config.HOST} -p {config.PORT}")

pytest.ini配置文件

[pytest]
# 指定运行参数
addopts = -s -v -p no:warnings  --alluredir=./results

# 搜索测试文件的目录路径
testpaths = ./

# 搜索测试文件名格式
python_files = test_*.py

# 搜索测试类格式
python_classes = Test*

# 搜索测试方法名格式
python_functions = test_*

下面编写一个测试用例,来确保框架可以正常运行

test_login.py

import allure
import config


@allure.epic(config.PROJECT_NAME)
@allure.feature("用户模块")
@allure.story("用户登录")
class TestUser(object):
    def test_username_by_empty(self):
        allure.dynamic.title("用户名为空,登录失败")
        allure.dynamic.description("测试用户名为空的描述")
        allure.attach("文件内容", "log")
        assert 1 == 2

运行main.py

 新增日志功能

日志模块是项目中必不可缺的模块,便于我们查看项目运行状况和排查错误

config.py新增日志配置

import pathlib  # 路径操作模块,替代os.path模块,可以通过对象的方式操作路径

# 项目目录的主目录文件路径[字符串]
BASE_DIR_STR = pathlib.Path(__file__).parent.resolve().as_posix()

# 项目目录的主目录路径[路径对象]
BASE_DIR = pathlib.Path(BASE_DIR_STR)

# 项目名字
PROJECT_NAME = "接口自动化测试框架"

# 自动化测试报告的运行IP和端口
HOST = "127.0.0.1"
PORT = 8088

# 日志模块配置
LOGGING = {
    "name": "Zeekr",  # 日志处理器名称
    "filename": (BASE_DIR / "logs/zeeker.log").as_posix(),  # 日志文件储存路径
    "charset": "utf-8",
    "backup_count": 31,  # 日志文件的备份数量
    "when": "d"  # 日志文件创建间隔时间为每天创建一个
}

if __name__ == '__main__':
    print(BASE_DIR_STR, type(BASE_DIR_STR))
    print(BASE_DIR, type(BASE_DIR))

logger.py模块

import logging
import config

from logging import handlers


class LogHandler(object):
    """日志处理工具类"""

    def __init__(self, name=None, filename=None):
        """
        :param name: 日志处理器的名字
        :param filename: 日志文件名字
        """
        # 如果没有指定name和filename,则使用配置文件配置
        self.name = name or config.LOGGING.get("name", "pytest")
        self.filename = filename or config.LOGGING.get("filename", "pytest.log")

        self.charset = config.LOGGING.get("charset", "utf-8")
        self.backup_count = config.LOGGING.get("backup_count", 31)
        self.when = config.LOGGING.get("when", "d")

        self.logger = None

    def get_logger(self):
        """
        创建 logger
        :return: logger 对象
        """
        # 避免重复创建logger TODO:使用到单例模式是否更好?
        if self.logger:
            return self.logger

        logger = logging.getLogger(self.name)
        # 设置日志初始化等级
        logger.setLevel(logging.DEBUG)

        # 创建handler
        fh = handlers.TimedRotatingFileHandler(
            filename=self.filename,
            when=self.when,
            backupCount=self.backup_count,
            encoding=self.charset
        )

        sh = logging.StreamHandler()

        # 为每种handler设置日志等级
        # fh.setLevel(logging.INFO)

        # 设置输出日志格式
        simple_formater = logging.Formatter(fmt="【{levelname}】 {name} {module}: {lineno} {message}", style="{")
        verbose_formater = logging.Formatter(
            fmt="【{levelname}】 {asctime} {name} {pathname}: {lineno} {message}",
            datefmt="%Y-%m-%d %H:%M:%S",
            style="{"
        )

        # 为handler指定输出格式
        fh.setFormatter(verbose_formater)
        sh.setFormatter(simple_formater)

        # 为logger添加日志处理器
        logger.addHandler(fh)
        logger.addHandler(sh)

        self.logger = logger

        return logger


if __name__ == '__main__':
    logger = LogHandler().get_logger()

    logger.debug("测试 debug")
    logger.info("测试 info")
    logger.warning("测试 warning")
    logger.error("测试 error")

在测试用例中简单使用日志

import allure
import config
from utils.logger import LogHandler

logger = LogHandler().get_logger()


@allure.epic(config.PROJECT_NAME)
@allure.feature("用户模块")
@allure.story("用户登录")
class TestUser(object):
    def test_username_by_empty(self):
        allure.dynamic.title("用户名为空,登录失败")
        allure.dynamic.description("测试用户名为空的描述")
        allure.attach("文件内容", "log")
        logger.debug("测试 debug")
        logger.info("测试 info")
        logger.warning("测试 warning")
        logger.error("测试 error")

运行main.py文件

 

【DEBUG】 2023-09-26 10:16:33 Zeekr /home/zk4956z3/PycharmProjects/Stark/utils/logger.py: 75 测试 debug
【INFO】 2023-09-26 10:16:33 Zeekr /home/zk4956z3/PycharmProjects/Stark/utils/logger.py: 76 测试 info
【WARNING】 2023-09-26 10:16:33 Zeekr /home/zk4956z3/PycharmProjects/Stark/utils/logger.py: 77 测试 warning
【ERROR】 2023-09-26 10:16:33 Zeekr /home/zk4956z3/PycharmProjects/Stark/utils/logger.py: 78 测试 error
【DEBUG】 2023-09-26 10:18:34 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 15 测试 debug
【INFO】 2023-09-26 10:18:34 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 16 测试 info
【WARNING】 2023-09-26 10:18:34 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 17 测试 warning
【ERROR】 2023-09-26 10:18:34 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 18 测试 error
【DEBUG】 2023-09-27 09:02:13 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 16 测试 debug
【INFO】 2023-09-27 09:02:13 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 17 测试 info
【WARNING】 2023-09-27 09:02:13 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 18 测试 warning
【ERROR】 2023-09-27 09:02:13 Zeekr /home/zk4956z3/PycharmProjects/Stark/tests/users/test_login.py: 19 测试 error

 

封装请求工具

接口的测试一般离不开http请求,在python中常用的http请求模块有urllib, requests,httpx等,不过小编最常用的还是requests

安装

pip install requests

简单使用

import requests

####### GET请求 ########
# 发送简单的get请求 response = requests.get("https://baidu.com")
# 发送有参数的get请求
params = {
"name": "kunmzhao",
"age":18
} response = requests.get("https://httpbin.org", params=params) # 获取原生内容 print(response.content) # 获取文本内容 print(response.text)

# 接收json格式内容
print(response.json())

# 接收二进制内容
with open("1.png", "wb") as fd:
  fd.write(response.content)

####### POST请求 ########
# 发送表单数据
import requests

forms = {"name":"kunmzhao","age":18}
response = requests.post("http://httpbin.org/post", data=forms)
print(response.text)

# 发送json数据
json_data = {"name":"kunmzhao","age":18}
response = requests.post("http://httpbin.org/post", json=json_data)
print(response.text)
# 文件上传 支持一张或者多张数据的上传
files = {"avata":open("1.png",'rb')}
response = requests.post("http://httpbin.org/post", files=files)
print(response.text)

##### 发送请求头 #####

headers = {
  "User-Agent":"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0"
}
response = requests.get("https://www/zhihu.com/explore", headers=headers)
print(response.text)

 

requestor.py 对常用的http请求操作进行封装

import requests
from utils.logger import LogHandler


class Request(object):
    """http请求工具类"""

    def __init__(self):
        # 实例化session管理器,维持会话
        self.session = requests.session()
        self.logger = LogHandler().get_logger()

    def send(self, method, url, params=None, data=None, json=None, headers=None, **kwargs):
        """
        发送http请求

        :param method: 请求方法
        :param url: 请求URL
        :param params: 请求参数
        :param data: 请求数据
        :param json: jason传参,请求数据
        :param headers: : 请求头
        :param kwargs: 其它参数
        :return: headers
        """
        try:
            self.logger.info(f"请求方法: {method}")
            self.logger.info(f"请求url: {url}")
            self.logger.info(f"请求params: {params}")
            self.logger.info(f"请求data: {data}")
            self.logger.info(f"请求json: {json}")
            self.logger.info(f"请求headers: {headers}")
            self.logger.info(f"请求额外参数: {kwargs}")

            response = self.session.request(method=method,
                                            url=url,
                                            params=params,
                                            data=data,
                                            json=json,
                                            headers=headers,
                                            **kwargs)
            self.logger.info(f"回复状态码: {response.status_code}")
            self.logger.info(f"回复响应头: {response.headers}")
            self.logger.info(f"回复响应体[二进制]: {response.content}")
            self.logger.info(f"回复响应体[纯文本]: {response.text}")
            self.logger.info(f"回复响应体[json]: {response.json()}")
            return response
        except Exception as e:
            self.logger.error(f"请求错误,错误信息:{e}")

    def __call__(self, method, url, params=None, data=None, json=None, headers=None, **kwargs):
        """
        将对象当做一个函数来使用,Request(method, url, params=None, data=None, json=None, headers=None, **kwargs)
        """
        return self.send(method=method,
                         url=url,
                         params=params,
                         data=data,
                         json=json,
                         headers=headers,
                         **kwargs)


if __name__ == '__main__':
    request = Request()
    res = request("GET", "http://httpbin.org/get")
    print(res)

    res = request("post", "http://httpbin.org/post", json={"username": "kunmzhao"})
    print(res)

 

基于Flask四线Mockserver

在实际的项目开发中,经常出现服务端和客户端分离的情况,我们在做测试开发的时候,有可能服务的功能还没有实现,我们可以自己先模拟出服务端返回结果,以实现联调,等服务器上线后,切换server即可

Flask是一个轻量级的python web框架,非常适合在测试中构建模拟api服务器

安装模块

pip install flask
pip install pymysql
pip install flask_sqlalchemy

 

api/__init__.py

import config
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

app = Flask(__name__)


def init_app():
    # 加载配置
    app.config.from_object(config)
    # 加载数据库配置
    db.init_app(app)
    # db创建数据库
    with app.app_context():
        db.create_all()

    return app

config.py

import pathlib  # 路径操作模块,替代os.path模块,可以通过对象的方式操作路径

# 项目目录的主目录文件路径[字符串]
BASE_DIR_STR = pathlib.Path(__file__).parent.resolve().as_posix()

# 项目目录的主目录路径[路径对象]
BASE_DIR = pathlib.Path(BASE_DIR_STR)

# 项目名字
PROJECT_NAME = "接口自动化测试框架"

# 自动化测试报告的运行IP和端口
HOST = "127.0.0.1"
PORT = 8089

# 日志模块配置
LOGGING = {
    "name": "Zeekr",  # 日志处理器名称
    "filename": (BASE_DIR / "logs/zeeker.log").as_posix(),  # 日志文件储存路径
    "charset": "utf-8",
    "backup_count": 31,  # 日志文件的备份数量
    "when": "d"  # 日志文件创建间隔时间为每天创建一个
}


"""mock server 的服务端配置"""
# 数据库连接
SQLALCHEMY_DATABASE_URI: str = "mysql+pymysql://root:[email protected]:3306/pytest?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO: bool = True
# 调试模式
DEBUG = True
# 监听端口
API_PORT = 8000
# 监听地址
API_HOST = "0.0.0.0"

if __name__ == '__main__':
    print(BASE_DIR_STR, type(BASE_DIR_STR))
    print(BASE_DIR, type(BASE_DIR))

api/models.py

from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash

from . import db


class BaseModel(db.Model):
    """公共模型"""
    __abstract__ = True  # 抽象模型
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), default="", comment="名称/标题")
    is_deleted = db.Column(db.Boolean, default=False, comment="逻辑删除")
    orders = db.Column(db.Integer, default=0, comment="排序")
    status = db.Column(db.Boolean, default=True, comment="状态(是否显示,是否激活)")
    created_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间")
    updated_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")

    def __repr__(self):
        return f"<{self.__class__.__name__}: {self.name}>"


class User(BaseModel):
    """用户基本信息表"""
    __tablename__ = "py_user"
    name = db.Column(db.String(255), index=True, comment="用户账户")
    nickname = db.Column(db.String(255), comment="用户昵称")
    _password = db.Column(db.String(255), comment="登录密码")
    intro = db.Column(db.String(500), default="", comment="个性签名")
    avatar = db.Column(db.String(255), default="", comment="头像url地址")
    sex = db.Column(db.SmallInteger, default=0, comment="性别")  # 0表示未设置,保密, 1表示男,2表示女
    email = db.Column(db.String(32), index=True, default="", nullable=False, comment="邮箱地址")
    mobile = db.Column(db.String(32), index=True, nullable=False, comment="手机号码")

    # 存取器
    @property
    def password(self):  # user.password
        return self._password

    @password.setter
    def password(self, rawpwd):  # user.password = '123456'
        """密码加密"""
        self._password = generate_password_hash(rawpwd)

    def check_password(self, rawpwd):
        """验证密码"""
        return check_password_hash(self.password, rawpwd)

api/views.py


from flask import request
from sqlalchemy import or_

from . import app
from .models import User,db


@app.route("/user/register", methods=["POST"])
def register():
"""
用户信息注册
:return:
"""
try:
data = request.json
# 创建用户数据
user = User(**data)
db.session.add(user)
db.session.commit()
return {"msg": "注册成功!", "data": {"id":user.id, "name": user.name}}, 200
except Exception as e:
return {"msg": "注册失败!", "data": {}}, 400


@app.route("/user/login", methods=["POST"])
def login():
"""
用户登录
:return:
"""
user = User.query.filter(
or_(
User.mobile == request.json.get("mobile"),
User.name == request.json.get("name"),
User.email == request.json.get("email")
)
).first() # 实例化模型

if not user:
return {"msg": "登录失败!用户不存在!", "data": {}}, 400
if not user.check_password(request.json.get("password")):
return {"msg": "登录失败!密码错误!", "data": {}}, 400

return {"msg": "登录成功", "data":{"id": user.id, "name": user.name}}, 200


api/run.py

import config
from api import init_app

# 注意,务必把模型models的内容以及 views 中的服务端接口引入当前文件,否则flask不识别。
from api import models
from api import views

app = init_app()

if __name__ == '__main__':
    app.run(host=config.API_HOST, port=config.API_PORT)

 

test_login.py

import allure
import config
from utils.logger import LogHandler
from utils.requestor import Request

logger = LogHandler().get_logger()
SERVER_URl = f"http://{config.API_HOST}:{config.API_PORT}"

@allure.epic(config.PROJECT_NAME)
@allure.feature("用户模块")
@allure.story("用户登录")
class TestUser(object):
    def test_username_by_empty(self):
        allure.dynamic.title("用户名为空,登录失败")
        allure.dynamic.description("测试用户名为空的描述")
        allure.attach("文件内容", "log")
        request = Request()
        response = request("POST", f"{SERVER_URl}/user/login", json={
            "name": "",
            "password": "123"
        })
        print(response)

    def test_password_by_empty(self):
        allure.dynamic.title("密码为空,登录失败")
        allure.dynamic.description("测试密码为空的描述")
        allure.attach("文件内容", "log")
        request = Request()
        response = request("POST", f"{SERVER_URl}/user/login", json={
            "name": "kunmzhao",
            "password": ""
        })
        print(response)

此时运行main.py文件和run.py文件,即可测该框架是否成功

 

基于数据驱动生成用例代码

在实际测试开发中,我们一般使用参数化来自动生成测试用例,参数化用例一般采用json,yaml或者excle文件储存,如果用例非常多,也可以改用数据库

下面介绍通过excel来承载测试用例,python中操作excel文件的模块有xlrd+xlwt,pyexcle+openpyxl

安装模块

pip install xlrd
pip install xlwt

 

封装excel工具类

excel.py

"""
不识别xlsx的文件后缀名,文件格式必须设置为xls
"""
import xlrd
import json


class Excel(object):
    """Excel文件写工具类, TODO:关于excel写操作后续补充"""

    def __init__(self, filename):
        self.workbook = xlrd.open_workbook(filename, formatting_info=True)

    def get_sheet_names(self):
        """
        获取当前excle中所有sheet的名字
        """
        return self.workbook.sheet_names()

    def __get_sheet(self, sheet_index_or_name):
        """
        根据sheet的索引或者名字获取对应sheet对象
        """
        if isinstance(sheet_index_or_name, int):
            if len(self.get_sheet_names()) > sheet_index_or_name:
                return self.workbook.sheet_by_index(sheet_index_or_name)
            raise Exception(
                "无效的的sheet下标数值, excel文档最大值为{}, 请求为数值为{}".format(
                    len(self.get_sheet_names()),
                    sheet_index_or_name),
            )
        elif isinstance(sheet_index_or_name, str):
            if sheet_index_or_name in self.get_sheet_names():
                return self.workbook.sheet_by_name(sheet_index_or_name)
            raise Exception("无效的sheet名字,名字为{}的sheet不存在".format(sheet_index_or_name))

        raise Exception("sheet_index_or_name只能为int或者str,但是入参为{}".format(type(sheet_index_or_name)))

    def get_sheet_rows_num(self, sheet_index_or_name):
        """
        获取指定sheet的的数据总行数
        """
        return self.__get_sheet(sheet_index_or_name).nrows

    def get_sheet_cols_num(self, sheet_index_or_name):
        """
        获取指定sheet的数据总列数
        """
        return self.__get_sheet(sheet_index_or_name).ncols

    def get_cell_value(self, sheet_index_or_name, row_index, col_index):
        """
        获取指定sheet中的指定单元格数据
        """
        sheet = self.__get_sheet(sheet_index_or_name)
        try:
            return sheet.cell_value(row_index, col_index)
        except Exception as e:
            raise Exception(str(e))

    def get_sheet_data(self, sheet_index_or_name, fields, first_line_is_header=True):
        """
        获取工作表的所有数据
        """
        rows = self.get_sheet_rows_num(sheet_index_or_name)
        cols = self.get_sheet_cols_num(sheet_index_or_name)

        data = []
        for row in range(int(first_line_is_header), rows):
            row_data = {}
            for col in range(cols):
                cell_data = self.get_cell_value(sheet_index_or_name, row, col)
                if type(cell_data) is str and ("{" in cell_data and "}" in cell_data) or (
                        "[" in cell_data and "]" in cell_data):
                    cell_data = json.loads(cell_data)
                print(col, fields[col])
                row_data[fields[col]] = cell_data
            data.append(row_data)
        return data


if __name__ == '__main__':
    xls = Excel("../data/外来人员.xls")
    fields = ["姓名", "身份证号", "手机号",
              "人员在胶状态", "目前详细住址", "随访异常",
              "随访异常备注", "是否已做核酸", "核酸结果",
              "核酸采样日期", "核酸采样地点", "人员类别",
              "重点地区", "到达重点地区时间", "离开重点地区时间",
              "返回胶州日期", "请输入行程详情概述"]

    print(xls.get_sheet_names())
    print(xls.get_sheet_cols_num("Sheet0"))
    print(xls.get_sheet_rows_num(0))
    print(xls.get_cell_value(0, 1, 1))
    print(xls.get_sheet_data(0, fields))

 

config.py

import pathlib  # 路径操作模块,替代os.path模块,可以通过对象的方式操作路径

# 项目目录的主目录文件路径[字符串]
BASE_DIR_STR = pathlib.Path(__file__).parent.resolve().as_posix()

# 项目目录的主目录路径[路径对象]
BASE_DIR = pathlib.Path(BASE_DIR_STR)

# 项目名字
PROJECT_NAME = "接口自动化测试框架"

# 自动化测试报告的运行IP和端口
HOST = "127.0.0.1"
PORT = 8089

# 日志模块配置
LOGGING = {
    "name": "Zeekr",  # 日志处理器名称
    "filename": (BASE_DIR / "logs/zeeker.log").as_posix(),  # 日志文件储存路径
    "charset": "utf-8",
    "backup_count": 31,  # 日志文件的备份数量
    "when": "d"  # 日志文件创建间隔时间为每天创建一个
}

# excel测试用例字段格式
FIELD_LIST = [
    "case_id",  # 用例编号
    "module_name",  # 模块名称
    "case_name",  # 用例名称
    "method",  # 请求方式
    "url",  # 接口地址
    "headers",  # 请求头
    "params_desc",  # 参数说明
    "params",  # 请求参数
    "assert_result",  # 预期结果
    "real_result",  # 实际结果
    "remark",  # 备注
]

"""mock server 的服务端配置"""
# 数据库连接
SQLALCHEMY_DATABASE_URI: str = "mysql+pymysql://root:[email protected]:3306/pytest?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO: bool = True

# 调试模式
DEBUG = True
# 监听端口
API_PORT = 8000
# 监听地址
API_HOST = "0.0.0.0"

if __name__ == '__main__':
    print(BASE_DIR_STR, type(BASE_DIR_STR))
    print(BASE_DIR, type(BASE_DIR))

基于excel文件实现数据驱动生成测试用例

data/case_user.xls

 

utils/assertor.py

from utils.logger import LogHandler

logger = LogHandler().get_logger()


def assertor(assert_list, response):
    """断言函数"""
    if type(assert_list) is not list:
        assert_list = [assert_list]

    for expr in assert_list:
        logger.info(f"开始断言:assert {expr}")
        if expr:
            # exec 内置解释器,可以把符合python语法的字符串当成代码来运行
            exec(f"assert {expr}", {
                "code": response.status_code,
                "json": response.json(),
                "text": response.text,
                "content": response.content,
                "headers": response.headers,
            })

        logger.info(f"断言通过:assert {expr}")


if __name__ == '__main__':
    # Response就是模拟requests HTTP请求工具的返回结果对象
    class Response(object):
        status_code = 400
        text = "对不起,登陆失败!"
        content = "对不起,登陆失败!"
        headers = []

        @classmethod
        def json(cls):
            return {"id": 1},


    assert_list = [
        "code == 400",
        "'失败'in text",
    ]

    assertor(assert_list, Response())

 

test_login.py

import allure
import pytest

import config
from utils.logger import LogHandler
from utils.requestor import Request
from utils.excle import Excel
from utils.assertor import assertor

logger = LogHandler().get_logger()
SERVER_URl = f"http://{config.API_HOST}:{config.API_PORT}"


@allure.epic(config.PROJECT_NAME)
@allure.feature("用户模块")
@allure.story("用户登录")
class TestUser(object):
    @pytest.mark.parametrize("kwargs",
                             Excel(config.BASE_DIR / "data/case_user.xls").get_sheet_data(0, config.FIELD_LIST))
    def test_login(self, kwargs):
        request = Request()
        allure.dynamic.title(kwargs.get("case_name"))
        request.logger.info(f"开始请求测试接口:{kwargs.get('case_name')}")
        if kwargs.get("method").lower() in ["get", "delete"]:
            """发送get或delete"""
            response = request(kwargs.get("method"), f'{kwargs.get("url")}', params=kwargs.get("params"))
        else:
            """发送post,put,patch"""
            response = request(kwargs.get("method"), f'{kwargs.get("url")}', json=kwargs.get("params"))
        assertor(kwargs.get("assert_result"), response)

 

标签:__,sheet,name,get,self,接口,测试,自动化,logger
From: https://www.cnblogs.com/victor1234/p/17728733.html

相关文章

  • list接口
    13.3.1List接口的定义publicinterfaceList<E>extendsCollection<E>13.3.2List接口的常用子类如果要使用List接口进行操作,就必须依靠子类实现对象的实例化操作,而在实际开发中List子接口有 ArrayList, Vector 和 LinkedList3个常用子类。1.新的子类:ArrayListArra......
  • destoon短信接口修改方法
    destoon是很优秀的B2B行业站程序。程序模块化开发契合度很高,二次开发起来也很顺畅。数据缓存,权限分配,SEO功能方面都不错。但是在使用这套程序的时候,常常要用到发送短信的功能,而destoon本身只接入了自己的短信接口。一些初接触destoon的开发者不知道如何修改。所以铁牛特此写个文......
  • 自动化构建:提高开发流程效率与质量的关键工具
    ......
  • 内网穿透:实现远程访问和测试内部网络的关键技术
    ......
  • 操作系统(3)---操作系统的接口、内核
    一、操作系统的接口  用户使用操作系统的方式:命令行 shell是提供用户使用界面的程序(命令解释器),包括图形界面shell和命令行shell。其作用有二:传递请求指令,让操作系统执行命令保护内核图形按钮图形界面是一个包括画图的c程序。每一个硬件动作相当于一......
  • 渗透测试实战-CS工具使用
    ★关于道德伦理的忠告★关于一些网络安全实战内容,我还是不厌其烦的写上这些忠告,请见谅。以下内容摘自《Metasploit渗透测试指南》作为一名渗透测试者,我们可以击败安全防御机制,但这是仅仅是我们工作的一部分。当你进行渗透攻击时,请记住如下的忠告:不要进行恶意的攻击;不要......
  • 〖Effective软件测试〗
    书籍封面Effective软件测试强调了测试的关键性。它提醒我们,软件测试不仅仅是一个附加项或者一项简单的任务,而是确保软件质量和可靠性的关键步骤。在现代软件开发中,软件产品越来越复杂,因此测试必须得到足够的重视和投入。这本书教导了我们如何将测试纳入整个开发周期,并将其视为一项......
  • c++ boost库安装与测试
    1、从官网http://www.boost.org/users/download/下载最新版本的boost,如boost_1_65_02、解压tarxzvfboost_1_65_0.tar.gz,3、安装cdboost_1_65_0/./bootstrap.shsudo./b2install 4、测试boost,以any类型和uuid为例。#include<iostream>#include<list>#include<ve......
  • 视频监控/安防监控EasyCVR平台如何调取登录接口获取token?
    安防视频监控平台EasyCVR是一个具有强大拓展性、灵活的视频能力和轻便部署的平台。它支持多种主流标准协议,包括国标GB28181、RTSP/Onvif、RTMP等,还可以支持厂家的私有协议和SDK接入,例如海康Ehome、海大宇等设备的SDK。该平台不仅拥有传统安防视频监控的功能,还具备接入AI智能分析的......
  • 【14.0】中间件、跨域资源共享、后台任务、测试用例
    【一】中间件【1】中间件介绍FastAPI中间件是在处理请求和响应的过程中介入的组件,允许你在请求到达处理函数之前或响应离开处理函数之后执行一些逻辑。中间件在FastAPI中起到非常灵活的作用,可以用于日志记录、身份验证、异常处理等。【2】中间件的工作原理(1)注册中间件......