Pytest基础框架梳理(未完待续)

Pytest基础框架

注:本文为学习笔记博,已成功搭建并在原基础上做了一些优化,框架源自:https://blog.csdn.net/tester_sc/article/details/118617901

API 接口管理模块

最基础的模块,存放 api 地址,便于日后查找和修改。将用例和配置分离是很有必要的。可以按照系统的模块来分类存放,例如 注册、登录、忘记密码等都可以放在User 类。

User.py

封装:
from common.GetConfig import baseUrl # 获取接口 host
from common.RestClient import RestClient # 发起请求的类

class User(RestClient):
    def __init__(self,baseUrl,**kwargs):
        super(User,self).__init__(baseUrl,**kwargs)

    def login(self,**kwargs):
        return self.post("/login",**kwargs)

    def register(self,**kwargs):
        return self.post("/register",**kwargs)

    def userInfo(self,**kwargs):
        return self.post("/info",**kwargs)


user = User(baseUrl)
调用:
import pytest
from api.User import user

class TestGetUserInfo:
    def test_01(self,login_fixture):

        header = {
            "Authorization": login_fixture.access_token
        }
        res = user.userInfo(headers = header)
        data = res.json()
        assert data['userLevel'] is not None

Common 公共模块

公共调用工具类,例如日志 Logger、发送请求封装类 RestClient、读取数据类 ReadData、获取环境配置类 GetConfig

Logger.py

可以输出或生成运行日志文件,方便问题定位。由 logging 库二次封装而来。

官方的 logging 模块工作流程图如下: 从下图中我们可以看出看到这几种 类型,Logger、LogRecord、Filter、Handler、Formatter。
类型说明:
Logger:日志,暴露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志有效。
LogRecord:日志记录器,将日志传到相应的处理器处理。
Handler :处理器, 将(日志记录器产生的)日志记录发送至合适的目的地。
Filter:过滤器, 提供了更好的粒度控制,它可以决定输出哪些日志记录。
Formatter:格式化器, 指明了最终输出中日志记录的布局。

封装
import logging,time,os

BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
LOG_PATH = os.path.join(BASE_PATH,'log')
if not os.path.exists(LOG_PATH):
    os.mkdir(LOG_PATH)

class LoggerTest:
    def __init__(self):
        #自定义日志对象
        self.logname = os.path.join(LOG_PATH,'{}.log'.format(time.strftime("%Y%m%d")))
        self.logger = logging.getLogger('log')
        self.logger.setLevel(level=logging.DEBUG)
        
        #生成 Handler对象
        self.console = logging.StreamHandler()
        self.filelogger = logging.FileHandler(filename=self.logname,mode= 'a')
        
        #设置 Handler日志等级
        self.console.setLevel(level= logging.ERROR)
        self.filelogger.setLevel(level=logging.DEBUG)
        
        #设置 Handler格式
        self.formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
        self.console.setFormatter(self.formatter)
        self.filelogger.setFormatter(self.formatter)
        
        #给自定义日志对象增加 Handler
        self.logger.addHandler(self.console)
        self.logger.addHandler(self.filelogger)

logger = LoggerTest().logger

if __name__ == '__main__':
    logger.debug('This is a customer debug message')
    logger.info('This is an customer info message')
    logger.warning('This is a customer warning message')
    logger.error('This is an customer error message')
    logger.critical('This is a customer critical message')


调用
from common.Logger import logger

logger.info("接口请求地址 ==>> {}".format(url))
logger.info("接口请求方式 ==>> {}".format(method))
logger.info("接口响应为 ==>> {}".format(complexjson.dumps(res, indent=4, ensure_ascii=False)))

RestClient.py

发送请求类,由 Request库封装而来,方便 API 类继承后直接发送请求,还可以让任何请求都被记录在 log 中。这里相较原文件添加了接口响应也打印到 log 中。

封装
import requests
import json as complexjson
from common.Logger import logger


class RestClient():

    def __init__(self, baseUrl):
        self.api_root_url = baseUrl
        self.session = requests.session()

    def get(self, url, **kwargs):
        return self.request(url, "GET", **kwargs)

    def post(self, url, data=None, json=None, **kwargs):
        return self.request(url, "POST", data, json, **kwargs)

    def put(self, url, data=None, **kwargs):
        return self.request(url, "PUT", data, **kwargs)

    def delete(self, url, **kwargs):
        return self.request(url, "DELETE", **kwargs)

    def patch(self, url, data=None, **kwargs):
        return self.request(url, "PATCH", data, **kwargs)

    def request(self, url, method, data=None, json=None, **kwargs):
        url = self.api_root_url + url
        headers = dict(**kwargs).get("headers")
        params = dict(**kwargs).get("params")
        files = dict(**kwargs).get("params")
        cookies = dict(**kwargs).get("params")
        self.request_log(url, method, data, json, params, headers, files, cookies)
        if method == "GET":
            res = self.session.get(url, **kwargs)
            self.response_log(res.json())
            return res
        if method == "POST":
            res = self.session.post(url, data, json, **kwargs)
            self.response_log(res.json())
            return res
        if method == "PUT":
            if json:
                # PUT 和 PATCH 中没有提供直接使用json参数的方法,因此需要用data来传入
                data = complexjson.dumps(json)
            return self.session.put(url, data, **kwargs)
        if method == "DELETE":
            return self.session.delete(url, **kwargs)
        if method == "PATCH":
            if json:
                data = complexjson.dumps(json)
            return self.session.patch(url, data, **kwargs)

    def request_log(self, url, method, data=None, json=None, params=None, headers=None, files=None, cookies=None, **kwargs):
        logger.info("接口请求地址 ==>> {}".format(url))
        logger.info("接口请求方式 ==>> {}".format(method))
        # Python3中,json在做dumps操作时,会将中文转换成unicode编码,因此设置 ensure_ascii=False
        if headers is not None:
            logger.info("接口请求头 ==>> {}".format(complexjson.dumps(headers, indent=4, ensure_ascii=False)))
        if params is not None:
            logger.info("接口请求 params 参数 ==>> {}".format(complexjson.dumps(params, indent=4, ensure_ascii=False)))
        if data is not None:
            logger.info("接口请求体 data 参数 ==>> {}".format(complexjson.dumps(data, indent=4, ensure_ascii=False)))
        if json is not None:
            logger.info("接口请求体 json 参数 ==>> {}".format(complexjson.dumps(json, indent=4, ensure_ascii=False)))
        # logger.info("接口上传附件 files 参数 ==>> {}".format(files))
        # logger.info("接口 cookies 参数 ==>> {}".format(complexjson.dumps(cookies, indent=4, ensure_ascii=False)))
    def response_log(self,res):
        logger.info("接口响应 ==>> {}".format(complexjson.dumps(res, indent=4, ensure_ascii=False)))
调用
#可在 User.py中看到

ReadFileData.py

可用于读取.ini .yaml .json文件

封装
import yaml
import json
from configparser import ConfigParser
from common.Logger import logger


class MyConfigParser(ConfigParser):
    # 重写 configparser 中的 optionxform 函数,解决 .ini 文件中的 键option 自动转为小写的问题
    def __init__(self, defaults=None):
        ConfigParser.__init__(self, defaults=defaults)

    def optionxform(self, optionstr):
        return optionstr

class ReadFileData():

    def __init__(self):
        pass

    def load_yaml(self, file_path):
        logger.info("加载 {} 文件......".format(file_path))
        with open(file_path, encoding='utf-8') as f:
            data = yaml.safe_load(f)
        logger.info("读到数据 ==>>  {} ".format(data))
        return data

    def load_json(self, file_path):
        logger.info("加载 {} 文件......".format(file_path))
        with open(file_path, encoding='utf-8') as f:
            data = json.load(f)
        logger.info("读到数据 ==>>  {} ".format(data))
        return data

    def load_ini(self, file_path):
        logger.info("加载 {} 文件......".format(file_path))
        config = MyConfigParser()
        config.read(file_path, encoding="UTF-8")
        data = dict(config._sections)
        # print("读到数据 ==>>  {} ".format(data))
        return data

data = ReadFileData()
调用
#请看 GetConfig.py

GetConfig.py

可用于获取环境配置信息

封装
import os
from common.ReadFileData import data

class GetConfig:
    def __init__(self):
        self.base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
        self.config_path = os.path.join(self.base_path, "config", "devtr.ini")
        self.config_data = data.load_ini(self.config_path)

    def setEnvir(self,envir):
        self.config_path = os.path.join(self.base_path, "config", envir)
        self.config_data = data.load_ini(self.config_path)

    def getHost(self):
        baseUrl = self.config_data['host']['baseUrl']
        return baseUrl


config = GetConfig()
baseUrl = config.getHost()
调用
from requests import Session
from common.GetConfig import baseUrl
from api.User import user

class Login():
    def __init__(self):
        self.s = Session()
        self.baseUrl = baseUrl
    def login(self,username,password):
        self.username = username
        self.password = password
        data = {
            "user": self.username,
            "password": self.password
        }
        res = user.login(json = data)
        r = res.json()
        if res.status_code == 200:
            self.userId = r['user']
        else:
            print(r)
    def refreshToken(self):
        res = user.refreshToken()
        r = res.json()
        if res.status_code == 200:
            self.access_token = r['access_token']
            self.refresh_token = r['refresh_token']
        else:
            print(res)

if __name__ == '__main__':
    username = '***'
    password = '***'
    user = Login(username,password)
    try:
        print(user.access_token)
        print('-------------------')
        print(user.refresh_token)
    except Exception as e:
        print('Login failed,because:')
        print(e)

Database.py

执行数据库操作的库

封装
import psycopg2
from common.GetConfig import config

class Database:
    def __init__(self):
        self.databaseConfig = config.config_data
        self.host = self.databaseConfig['database']['HOST']
        self.port = self.databaseConfig['database']['PORT']
        self.user = self.databaseConfig['database']['USER']
        self.passwd = self.databaseConfig['database']['PASSWD']

    def connect(self,database):
        '''连接串'''
        self.conn = psycopg2.connect(host = self.host,port = self.port,database = database,user = self.user,password = self.passwd)

    def select(self,sql):
        '''开启游标'''
        self.cur = self.conn.cursor()
        '''执行语句'''
        self.cur.execute(sql)
        '''获取结果集的每一行'''
        rows = self.cur.fetchall()
        '''关闭游标'''
        self.cur.close()
        return rows

    def update(self,sql):
        '''开启游标'''
        self.cur = self.conn.cursor()
        '''执行语句'''
        self.cur.execute(sql)
        '''关闭游标'''
        self.cur.close()
        '''提交'''
        self.conn.commit()

    def __exit__(self, exc_type, exc_val, exc_tb):
        '''关闭连接'''
        self.conn.close()

if __name__ == '__main__':
    database = Database()
    database.connect('database_name')
    sql1 = "***"
    sql2 = "***"
    database.update(sql1)
    rows = database.select(sql2)

    for row in rows:
        print(row)
调用
from common.Database import Database

database = Database()
database.connect('database_name')
sql1 = "***"
sql2 = "***"
database.update(sql1)
rows = database.select(sql2)

for row in rows:
    print(row)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值