接口自动化-用例结构设计和框架设计

本文档描述了一个自动化测试框架,包括登录接口、新增、修改、删除和查询接口的信息。测试用例存储在数据库中,框架包含了数据替换、关联处理、执行结果记录和断言等功能。此外,还提供了日志、报告和数据库配置的相关信息。
摘要由CSDN通过智能技术生成

登录接口信息

在这里插入图片描述

新增接口信息

在这里插入图片描述

修改接口信息

在这里插入图片描述

删除接口信息

在这里插入图片描述

查询接口信息

在这里插入图片描述

测试用例存储字段

CREATE TABLE `test_case_list` (# 测试用例的编号,不为空,自增长
	`id` INT ( 0 ) NOT NULL AUTO_INCREMENT,# 项目名称
	`web` VARCHAR ( 255 ) DEFAULT NULL,# 项目模块
	`module` VARCHAR ( 255 ) DEFAULT NULL,# 测试用例的标题
	`title` VARCHAR ( 255 ) DEFAULT NULL,# 接口地址的路径
	`url` VARCHAR ( 255 ) DEFAULT NULL,# 请求方法
	`method` VARCHAR ( 255 ) DEFAULT NULL,# 请求头
	`headers` VARCHAR ( 255 ) DEFAULT NULL,# cookies 秘钥
	`cookies` VARCHAR ( 1000 ) DEFAULT NULL,# 请求主体信息
	`request_body` VARCHAR ( 1000 ) DEFAULT NULL,# 请求主体的数据类型
	`request_type` VARCHAR ( 255 ) DEFAULT NULL,# 关联
	`relation` VARCHAR ( 255 ) DEFAULT NULL,# 预期业务状态码
	`expected_code` VARCHAR ( 255 ) DEFAULT NULL COMMENT ' 作为
	断言标准 ',# 测试用例是否可运行
	`isdel` INT ( 0 ) NULL DEFAULT 1 COMMENT '0 为删除, 1 为正常',# 设置 id 为主键
	PRIMARY KEY ( `id` ) USING BTREE # 设置表的引擎为 InnoDB

) ENGINE = INNODB;

配置信息存储字段

CREATE TABLE `test_config` (# 配置信息序号
	`id` INT ( 0 ) NOT NULL,# 项目名称
	`web` VARCHAR ( 255 ) DEFAULT NULL,# 环境信息字段
	`key` VARCHAR ( 255 ) DEFAULT NULL,# 环境信息的值
	`value` VARCHAR ( 255 ) DEFAULT NULL,# 设置 id 为主键
	PRIMARY KEY ( `id` ) USING BTREE # 设置表的引擎为 InnoDB

) ENGINE = INNODB;

执行结果记录存储字段

CREATE TABLE `test_result_record` (# 执行结果记录的序号,不为空,自增长
	`id` INT ( 0 ) UNSIGNED NOT NULL AUTO_INCREMENT,# 被执行测试用例的 id
	`case_id` VARCHAR ( 255 ) DEFAULT NULL,# 执行结果更新的时间
	`times` VARCHAR ( 255 ) DEFAULT NULL,# 程序运行的实际结果
	`response` VARCHAR ( 1000 ) DEFAULT NULL COMMENT '实际结果',# 用例执行是否通过
	`result` VARCHAR ( 255 ) DEFAULT NULL,# 设置 id 为主键
	PRIMARY KEY ( `id` ) USING BTREE # 设置表的引擎为 InnoDB

) ENGINE = INNODB;

框架层次结构

common

base.py
import json
from string import Template
import re
# 根据参数匹配内容
def find(data):
    # 判断data类型是否为字典
    if isinstance(data, dict):
        # 对象格式化为str
        data = json.dumps(data)
        # 定义正则匹配规则
        pattern = "\\${(.*?)}"
        # 匹配进行查询,把查询的结果返回
        return re.findall(pattern, data)
# 进行参数替换
def replace(ori_data, replace_data):
    # 对象格式化为str
    ori_data = json.dumps(ori_data)
    # 处理字符串的类,实例化并初始化原始字符
    s = Template(ori_data)
    # 使用新的字符,替换
    return s.safe_substitute(replace_data)
# 根据var,逐层获取json格式的值
def parse_relation(var, resdata):
    # 判断变量var是否存在
    if not var:
        # 不存在,直接返回resdata内容
        return resdata
    else:
        # 存在,获取数组第一个内容
        resdata = resdata.get(var[0])
        # 从数组中删除第一个内容
        del var[0]
        # 递归
        return parse_relation(var, resdata)
if __name__ == '__main__':
    ori_data = {"admin-token": "${token}"}
    replace_data = {'token': 'x015k878'}
    print(replace(ori_data, replace_data))

config

settings.py
import os
# 获取文件绝对路径
abs_path = os.path.abspath(__file__)
# 文件所在目录的上一级目录,获取根目录
project_path = os.path.dirname(os.path.dirname(abs_path))
# sep获取config目录的全路径
_conf_path = project_path + os.sep + "config"
# sep获取log目录的全路径
_log_path = project_path + os.sep + "log"
# sep获取report目录的全路径
_report_path = project_path + os.sep + "report"
# 数据库配置信息
DB_CONFIG = {
    "host": "192.168.70.130",
    "user": "root",
    "password": "123456",
    "database": "test",
    "charset": "utf8",
    "port": 33506
}
# 返回日志目录
def get_log_path():
    return _log_path
# 返回报告目录
def get_report_path():
    return _report_path
# 返回config目录
def get_config_path():
    return _conf_path
# test_run执行中,实现动态设置,获取属性,实现关联变量
class DynamicParam:
    pass
if __name__ == '__main__':
    print(get_config_path())

log

report
report.html

testcase

test_run.py
import datetime
from config.settings import DynamicParam
from utils.logutil import logger
import common.base as Base
import json
import pytest
from utils.readmysql import RdTestcase
from utils.requestsutil import RequestSend
# 初始化类
attribute = DynamicParam()
# 实例化测试用例对象
case_data = RdTestcase()
# 根据测试用例对象获取测试用例列表
case_list = case_data.is_run_data('zrlog')
# 获取当前时间
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 测试用例执行类
class TestApi:
    # 类方法,运行前只执行一次
    def setup_class(self):
        # 日志
        logger.info(f"----开始执行测试用例,开始时间为:{current_time}----")
    # 结束后只执行一次
    def teardown_class(self):
        logger.info(f"----测试用例执行完毕,完成时间为:{current_time}----")
    # 测试用例参数化
    @pytest.mark.parametrize('case', case_list)
    def test_run(self, case):
        # 定义变量
        res_data = None
        # 根据条件,从数据库获取url信息,然后拼接完整url信息
        url = case_data.loadConfkey('zrlog', 'url_api') ['value'] + case['url']
        # 获取method内容
        method = case['method']
        # 获取headers内容,格式化字符为字典
        headers = eval(case['headers'])
        # 获取cookies内容,格式化字符为字典
        cookies = eval(case['cookies'])
        # 获取请求内容,格式化字符问字典
        data = eval(case['request_body'])
        # 获取关联内容
        relation = str(case['relation'])
        # 获取测试用例名称
        case_name = case['title']
        # 根据关联获取headers参数中是否有变量被替换,有则替换,无则默认
        headers = self.correlation(headers)
        # 根据关联获取cookies参数中是否有变量被替换,有则替换,无则默认
        cookies = self.correlation(cookies)
        # 根据关联获取data参数中是否有变量被替换,有则替换,无则默认
        data = self.correlation(data)
        # 异常处理
        try:
            # 日志
            logger.info("正在执行{}用例".format(case_name))
            # 执行测试用例,发送HTTP请求
            res_data = RequestSend().send(url, method, data=data, headers=headers, cookies=cookies)
            # 日志
            logger.info("用例执行成功,请求的结果为{}".format(res_data))
        except:
            # 日志
            logger.info("用例执行失败,请查看日志找原因。")
            # 断言结果为失败
            assert False
        # 判断res_data是否存在
        if res_data:
            # res_data存在后,判断relation不为None
            if relation != "None":
                # 设置变量token的值为响应结果的信息
                self.set_relation(relation, res_data)
        # 对结果进行验证
        self.assert_respoes(case, res_data)
        return res_data
    # 响应结果关联设置函数
    def set_relation(self, relation, res_data):
        try:
            # 判断relation内容为True
            if relation:
                # 根据,进行分割,结果为list
                relation = relation.split(",")
                # 循环打印relation列表
                for i in relation:
                    # 根据=进行分割
                    var = i.split("=")
                    # 列表第一个值设置为var_name
                    var_name = var[0]
                    # 列表第二个值内容按.进行分割,内容保存到变量var_tmp
                    var_tmp = var[1].split(".")
                    # 响应结果在res_data中,根据var_tmp进行匹配
                    res = Base.parse_relation(var_tmp, res_data)
                    # 打印
                    print(f"{var_name}={res}")
                    # 把定义的变量名称和值以属性的方式设置到DynamicParam类中,实现动态存储
                    setattr(DynamicParam, var_name, res)
        except Exception as e:
            print(e)
    # 根据关联,获取该变量内容
    def correlation(self, data):
        # 根据正则,获取数据
        res_data = Base.find(data)
        # 判断1res_data为True
        if res_data:
            # 定义空的字典
            replace_dict = { }
            # 循环打印
            for i in res_data:
                # 根据名称,从DynamicParam动态获取属性值,并把内容赋值给变量data_tmp
                data_tmp = getattr(DynamicParam, str(i), "None")
                # 把结果更新到字典replace_dict中
                replace_dict.update({str(i): data_tmp})
            data = json.loads(Base.replace(data, replace_dict))
        return data
    # 结果验证方法
    def assert_respoes(self, case, res_data):
        # 变量初始化为False
        is_pass = False
        try:
            # 根据结果进行断言验证
            assert int(res_data['body']['error']) == int(case['expected_code'])
            # 日志
            logger.info("用例断言成功")
            # 设置变量为True
            is_pass = True
        except:
            # 设置变量为False
            is_pass = False
            # 日志
            logger.info("用例断言失败")
        finally:
            # 把结果更新到数据库
            case_data.updateResults(res_data, is_pass, str(case['id']))
            # 根据结果是T还是F进行断言
            assert is_pass
        return is_pass
if __name__ == '__main__':
    pytest.mark(['-s', '-v', 'test_run.py'])

utils

logutil.py
from config.settings import get_log_path
import logging
import time
import os
# 控制日志是否输出
STREAM = True
class LogUtil:
    def __init__(self):
        # 初始化日志对象,设置日志名称
        self.logger = logging.getLogger("logger")
        # 设置总体日志级别
        self.logger.setLevel(logging.DEBUG)
        # 避免日志重复
        if not self.logger.handlers:
            # 定义日志名称
            self.log_name = '{}.log'.format(time.strftime("%Y_%m_%d", time.localtime()))
            # 定义日志路径及文件名称
            self.log_path_file = os.path.join(get_log_path(), self.log_name)
            # 定义文件处理handler
            fh = logging.FileHandler(self.log_path_file, encoding='utf-8', mode='w')
            # 设置文件处理handler的日志级别
            fh.setLevel(logging.DEBUG)
            # 日志格式变量
            formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
            # 设置打印格式
            fh.setFormatter(formatter)
            # 添加handler
            self.logger.addHandler(fh)
            # 关闭handler
            fh.close()
            # 控制台输出
            if STREAM:
                # 定义控制台输出流handler
                fh_stream = logging.StreamHandler()
                # 输出日志级别
                fh_stream.setLevel(logging.DEBUG)
                # 设置打印格式
                fh_stream.setFormatter(formatter)
                # 添加handler
                self.logger.addHandler(fh_stream)
    def log(self):
        #  返回定义好的logger对象,对外直接使用
        return self.logger
# 其他程序可直接调用logger对象
logger = LogUtil().log()
# 测试
if __name__ == '__main__':
    logger.info('test')
mysqlutil.py
import pymysql
from config.settings import DB_CONFIG
from utils.logutil import logger
class MysqlUtil:
    def __init__(self):
        # 初始化pymysql数据库连接+创建游标
        self.db = pymysql.connect(**DB_CONFIG)
        self.cursor = self.db.cursor(cursor=pymysql.cursors.DictCursor)
    # 单条数据
    def get_fetchone(self, sql):
        # 执行sql
        self.cursor.execute(sql)
        # 查询单条数据,返回结果
        return self.cursor.fetchone()
    # 多条数据
    def get_fetchall(self, sql):
        # 执行sql
        self.cursor.execute(sql)
        # 查询多条数据,返回结果
        return self.cursor.fetchall()
    # 执行更新类sql
    def sql_execute(self, sql):
        try:
            # db和游标同事存在
            if self.db and self.cursor:
                # 执行sql,insert或update操作
                self.cursor.execute(sql)
                self.db.commit()
        except Exception as e:
            # 出异常回滚
            self.db.rollback()
            logger.error("sql执行错误,已执行回滚操作")
            return False
    # 关闭对象
    @staticmethod
    def close(self):
        # 游标是否存在,存在则关闭指针
        if self.cursor is not None:
            self.cursor.close()
        # 数据库对象是否存在,存在则关闭
        if self.db is not None:
            self.db.close()
# 测试
if __name__ == '__main__':
    mysql = MysqlUtil()
    res = mysql.get_fetchall("select * from test_case_list")
    print(res)
readmysql.py
import datetime
import json
from utils.mysqlutil import MysqlUtil
from utils.logutil import logger
# 初始化mysql工具类
mysql = MysqlUtil()
class RdTestcase:
    # 加载所有的测试用例
    def load_all_case(self, web):
        sql = f"select * from test_case_list where web = '{web}'"
        # 调取工具类方法,获取所有数据
        results = mysql.get_fetchall(sql)
        return results
    # 筛选可以执行的测试用例
    def is_run_data(self, web):
        # 根据条件isdel==1筛选可执行的测试用例
        run_list = [case for case in self.load_all_case(web) if case['isdel'] == 1]
        return run_list
    # 获取配置信息
    def loadConfkey(self, web, key):
        # 根据web, key查询test_config相关配置信息
        sql = f"select * from test_config where web = '{web}' and `key` = '{key}'"
        # 调取方法查询1条结果
        results = mysql.get_fetchone(sql)
        return results
    # 更新测试结果
    def updateResults(self, response, is_pass, case_id):
        # 获取当前时间
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # 更新测试用例执行结果,插入test_result_record表
        sql = f"insert into test_result_record (case_id,times,response,result) values ('{case_id}','{current_time}','{json.dumps(response, ensure_ascii=False)}','{is_pass}')"
        # 执行insert
        rows = mysql.sql_execute(sql)
        logger.debug(sql)
        # 返回True/False
        return rows
if __name__ == '__main__':
    test = RdTestcase()
    res = test.updateResults(
        {'code': 200,
         'body': {
             'error': 1,
             'message': '用户名和密码都不能为空'},
         'cookies': {}
         },
        'True', '4565'
    )
    print(res)
requestutil.py
import requests
from utils.logutil import logger
class RequestSend:
    # 封装requests请求函数
    def api_run(self, url, method, data=None, headers=None, cookies=None):
        # 定义变量,获取响应结果
        res = None
        # 打印日志
        logger.info("请求的url为{},类型为{}".format(url, type(url)))
        logger.info("请求的method为{},类型为{}".format(method, type(method)))
        logger.info("请求的data为{},类型为{}".format(data, type(data)))
        logger.info("请求的headers为{},类型为{}".format(headers, type(headers)))
        logger.info("请求的cookies为{},类型为{}".format(cookies, type(cookies)))
        # 判断请求方法
        if method == "get":
            res = requests.get(url, data=data, headers=headers, cookies=cookies)
        elif method == "post":
            # 判断请求的数据类型是否是json
            if headers == {"Content-Type": "application/json"}:
                # 发送HTTP请求,方法为post,参数使用json=data
                res = requests.post(url, json=data, headers=headers, cookies=cookies)
            elif headers == {"Content-Type": "application/x-www-form-urlencoded"}:
                # 发送HTTP请求,方法为post,参数使用data=data
                res = requests.post(url, data=data, headers=headers, cookies=cookies)
        # 获取请求响应的状态码
        code = res.status_code
        # 获取请求响应的cookies
        cookies = res.cookies.get_dict()
        # 定义字典
        dict1 = dict()
        # 异常处理
        try:
            # 获取响应结果json格式
            body = res.json()
        except:
            # 获取响应结果text
            body = res.text
        # 自定义参数写入字典
        dict1['code'] = code
        dict1['body'] = body
        dict1['cookies'] = cookies
        return dict1
    # 对外调用 **kwargs传入的参数是dict类型
    def send(self, url, method, **kwargs):
        # 调用自定义方法
        return self.api_run(url=url, method=method, **kwargs)
if __name__ == '__main__':
    url = "http://192.168.70.130/admin/login"
    data = {
        "username": "admin",
        "password": "b53685e590d5a7c71a24ebc592b49bf0",
        "https": False,
        "key": 1655363186867
    }
    method = "post"
    headers = {"Content-Type": "application/json"}
    print(RequestSend().send(url=url, method=method, headers=headers, data=data))

pytest.ini

[pytest]
addopts = -s -v --html=../report/report.html
testpaths = testcase
python_files = test_*.py
python_classes = Test*
python_functions = test*
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值