zoukankan      html  css  js  c++  java
  • pytest接口自动化搭建经验

      前言:目前公司的主要产品是一个web类型的产品;需要做一些自动化,目前的想法是只做接口自动化,不做ui的一个自动化,目前的思路是先对主流程做正常校验,后期再对每一个接口做校验;

      一、版本信息:

        python版本:3.8.6

        其他用:pip install -r requirements.txt

        requirements.txt

    allure-pytest==2.8.18
    allure-python-commons==2.8.18
    atomicwrites==1.4.0
    attrs==20.2.0
    certifi==2020.6.20
    cffi==1.14.2
    chardet==3.0.4
    colorama==0.4.3
    configobj==5.0.6
    configparser==5.0.0
    cryptography==3.1
    enum34==1.1.10
    ffmpy==0.2.3
    gevent==20.9.0
    greenlet==0.4.17
    idna==2.10
    importlib-metadata==1.7.0
    iniconfig==1.0.1
    lxml==4.5.2
    more-itertools==8.5.0
    namedlist==1.8
    packaging==20.4
    pluggy==0.13.1
    py==1.9.0
    pycparser==2.20
    pyOpenSSL==19.1.0
    pyparsing==2.4.7
    pytest==6.0.2
    pytest-html==2.1.1
    pytest-metadata==1.10.0
    pytest-ordering==0.6
    pytest-pythonpath==0.7.3
    pytils==0.3
    PyYAML==5.3.1
    requests==2.24.0
    selenium==3.141.0
    six==1.15.0
    toml==0.10.1
    tools==0.1.9
    urllib3==1.25.10
    zipp==3.1.0
    zope.event==4.5.0
    zope.interface==5.2.0

     

      二、项目结构:

        

        具体模块介绍:

    Common:基础方法文件夹
        assert.py:重写assert方法
        commonMethod.py:存一些常用方法,目前存了循环拿key为xx的value值
        conf.py:读写conf配置方法
        consts.py:计划用来存放全局变量,目前也用不上,可用来运行调试代码
        emailSend.py:发送邮件方法
        log.py:生成日志方法
        multitasks.py:多任务方法,目前还没写
        request.py:请求方法
        yamls.py:读写yaml文件方法
    Conf:存放配置
        conf.ini: 存放配置内容;
    Data:存放程序中需要用到的文件;
    Log:存放日志
    Params:存放测试用例,以yaml形式存储,按模块新建文件夹,存放用例
    Report:存放allure数据及报告
        allure-reports:存放allure数据
        html:allure数据解析成html文件
    Testcase:测试方法,以模块来定义py文件及文件名
    gitignore:上传git时忽略哪些文件
    conftest.py:pytest框架默认文件,可存放fixture配置文件
    requirements.txt: 项目中所用的依赖包;
    run.py:启动文件

       

      三、公共模块代码;

        assert.py

    # 重写assert方法
    from Common.log import logger
    
    
    class Assert:
        def assertEquals(self, actual, expected):
            """
            :param actual:实际值
            :param expected: 期望值
            :return:
            """
            try:
                assert str(actual) == str(expected)
                logger.info("断言成功,实际值:{} 等于预期值{}".format(actual, expected))
            except AssertionError as e:
                logger.error("断言失败,实际值:{}不等于预期值:{}".format(actual, expected))
                raise e
    
        def assertTrue(self, actual):
            '''
            :param actual: 实际值
            :return:
            '''
            try:
                assert actual == True
                logger.info("断言成功,实际值:{}为真".format(actual))
            except AssertionError as e:
                logger.error("断言失败,实际值:{}不为真".format(actual))
                raise e
    
        def assertIn(self, content, target):
    
            try:
                assert content in target
                logger.info("断言成功,目标文本:{}包含文本:{}".format(target, content))
            except AssertionError as e:
                logger.error("断言失败,目标文本:{}不包含文本:{}".format(target, content))
    View Code

        commonMethod.py

    # 循环拿key为objkey的value值
    def get_value(data, objkey, store_data):
        if isinstance(data, dict):
            for k, v in data.items():
                if k == objkey and v:
                    store_data.append(v)
                else:
                    if isinstance(v, dict) or isinstance(v, list):
                        get_value(v, objkey, store_data)
        elif isinstance(data, list):
            for i in data:
                if isinstance(data, dict) or isinstance(data, list):
                    get_value(i, objkey, store_data)
        return store_data
    View Code

        conf.py

    # -*- coding: utf-8 -*- 
    # @Time : 2020/6/3 15:48 
    # @Author : Jackyuan 
    # @File : conf.py
    
    from configobj import ConfigObj
    import os
    
    
    class Config:
        curpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        cfgpath = os.path.join(curpath, "./Conf/conf.ini")
        confObj = ConfigObj(cfgpath, encoding='utf-8')
    
        @classmethod
        def config(self, section):
            return self.confObj[section]
    
        @classmethod
        def setToken(self, value):
            self.confObj['TOKEN']['token'] = value
            self.confObj.write()
    
        @classmethod
        def setMyInfo(self, value):
            self.confObj['MYINFO']['myinfo'] = value
            self.confObj.write()
    
    
    if __name__ == '__main__':
        print(Config.config("ENV"))
    View Code

        emailSend.py

    # 封装发送邮件方法
    import smtplib
    from email.mime.text import MIMEText
    from email.header import Header
    from email.mime.multipart import MIMEMultipart
    from Common.conf import Config
    from Common.log import logger
    
    
    email_conf = Config.config("EMAIL")
    
    
    class SendEmail(object):
        def __init__(self):
            # 连接smtp服务器
            self.smtpObj = smtplib.SMTP_SSL(email_conf['mail_host'], email_conf['port'])
            # 登录smtp服务器
            self.smtpObj.login(email_conf['mail_user'], email_conf['mail_pwd'])
    
        # 构建不带附件的邮件正文
        def content_email(self, content):
            # 构建文件正文
            message = MIMEText(content, 'plain', 'utf-8')
            # 添加发件人
            message['From'] = Header(email_conf["sender"], 'utf-8')
            # 添加收件人
            message['To'] = Header(",".join(email_conf["receivers"]), "utf-8")
            # 添加邮件主题
            message['subject'] = Header(email_conf["subject"], 'utf-8')
            # 发送邮件
            self.send_email(message)
    
        # 构建带附件的邮件
        def attr_email(self, content, file_url):
            message = MIMEMultipart()
            # 添加发件人
            message['From'] = Header(email_conf["sender"], 'utf-8')
            # 添加收件人
            message['To'] = Header(",".join(email_conf["receivers"]), "utf-8")
            # 添加邮件主题
            message['subject'] = Header(email_conf["subject"], 'utf-8')
            # 创建邮件正文及附件
            message.attach(MIMEText(content, "plain", "utf-8"))
            # 构造附件1
            att1 = MIMEText(open(file_url, 'rb').read(), 'html', 'utf-8')
            att1["Content-Type"] = 'application/octet-stream'
            # 这里的filename可以任意写,写什么名字,邮件中显示什么名字
            att1["Content-Disposition"] = 'attachment; filename="test.html"'
            message.attach(att1)
            # 发送邮件
            self.send_email(message)
    
        # 发送邮件
        def send_email(self, message):
            try:
                self.smtpObj.sendmail(email_conf["sender"], email_conf["receivers"], message.as_string())
            except Exception as e:
                logger.error("发送带附件的邮件失败:{}".format(e))
    View Code

        log.py

    # 日志收集设置
    import logging, os
    from logging.handlers import TimedRotatingFileHandler
    import datetime
    
    current_dir = os.path.abspath(os.path.dirname(__file__))
    parent_dir = os.path.dirname(current_dir)
    my_log_path = os.path.join(parent_dir, "./Log")
    if not os.path.exists(my_log_path):
        os.mkdir(my_log_path)
    my_report_path = os.path.join(parent_dir, "./Report")
    if not os.path.exists(my_report_path):
        os.mkdir(my_report_path)
    
    # 定义一个日志收集器
    logger = logging.getLogger("guoguo")
    # 设置收集器级别,不设定的话,会默认搜集warning及以上级别的日志
    logger.setLevel(logging.INFO)
    # 设置日志格式
    fmt = logging.Formatter("%(filename)s-%(lineno)d-%(asctime)s-%(levelname)s-%(message)s")
    
    # 设置日志输出到控制台
    stream_handler = logging.StreamHandler()
    # 设置日志输出到文件
    file_handler = TimedRotatingFileHandler('Log/{}.log'.format(datetime.datetime.now().strftime('%Y-%m-%d')), when="D", interval=1, backupCount=30, encoding='utf-8')
    
    # 设置控制台和文件的日志输出格式
    stream_handler.setFormatter(fmt)
    file_handler.setFormatter(fmt)
    
    # 将输出对象添加到logger中
    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)
    """
    收集日志
    logger.info()
    logger.debug()
    logger.warning()
    logger.error()
    """
    View Code

        requests.py

    # 封装请求方法
    import requests
    from Common.log import logger
    from Common.conf import Config
    
    
    # 获取配置文件中的token
    token = {"token": Config.config("TOKEN")['token']}
    
    
    class RequestMethod(object):
        # 请求日志
        def api_log(self, method, url, headers=None, params=None, data=None, files=None, code=None, res_header=None, res_text=None, time=None):
            logger.info("请求方式===>{}".format(method))
            logger.info("请求路径===>{}".format(url))
            logger.info("请求头===>{}".format(headers))
            logger.info("请求参数===>{}".format(params))
            logger.info("请求体===>{}".format(data))
            logger.info("上传的文件内容===>{}".format(files))
            logger.info("响应状态码===>{}".format(code))
            logger.info("响应头===>{}".format(res_header))
            logger.info("响应体===>{}".format(res_text.decode("utf-8")))
            logger.info("响应时间===>{}".format(time))
    
        # get请求
        def get_main(self, url, headers, params=None):
            try:
                response = requests.get(url=url, headers=headers, params=params)
                return response
            except Exception as e:
                logger.error("{}该路径get请求出错,错误原因{}".format(url, e))
    
        # post请求
        def post_main(self, url, headers, params=None, data=None, files=None):
            try:
                response = requests.post(url=url, headers=headers, params=params, data=data, files=files)
                return response
            except Exception as e:
                logger.error("{}该路径post请求出错,错误原因:{}".format(url, e))
    
        # put请求
        def put_main(self, url, headers, params=None, data=None, files=None):
            try:
                response = requests.put(url=url, headers=headers, params=params, data=data, files=files)
                return response
            except Exception as e:
                logger.error("{}该路径put请求出错,错误原因{}".format(url, e))
    
        # delete请求
        def delete_main(self, url, headers, params=None):
            try:
                response = requests.delete(url=url, headers=headers, params=params)
                return response
            except Exception as e:
                logger.error("{}该路径delete请求出错,错误原因{}".format(url, e))
    
        # 选择调用方法
        def run_main(self, method, url, headers=None, data=None, params=None, files=None):
            if headers is None:
                headers = token
            else:
                headers.update(token)
            if method == "get":
                response = self.get_main(url, headers, params=params)
            elif method == "post":
                response = self.post_main(url, headers, params=params, data=data, files=files)
            elif method == "put":
                response = self.put_main(url, headers, params=params, data=data, files=files)
            elif method == "delete":
                response = self.delete_main(url, headers, params=params)
            else:
                logger.error("请求方式有误")
            self.api_log(method=method, url=response.url, headers=headers, params=params, data=data, files=None, code=response.status_code, res_header=response.headers, res_text=response.content, time=response.elapsed.total_seconds())
            return response
    View Code

        yamls.py

    import yaml
    from Common.log import logger
    
    
    class ReadYaml(object):
        def __init__(self, file_url, write_data=None):
            self.file_url = file_url
            self.write_data = write_data
    
        # 读取yaml数据
        def read_yaml(self):
            file_data = None
            try:
                with open(self.file_url, "r", encoding='utf-8') as f:
                    file_data = yaml.safe_load(f.read())
                logger.info("读取{}数据成功,读取数据为:
    {}".format(self.file_url, file_data))
            except Exception as e:
                logger.error("{}读取失败,失败原因为:{}".format(self.file_url, e))
            return file_data
    
        # 向yaml写入数据
        def write_yaml(self, method):
            if method == "append":
                method = "a+"
            elif method == "write":
                method = 'w+'
            else:
                return "method錯誤"
            try:
                with open(self.file_url, method, encoding="utf-8") as f:
                    yaml.dump(self.write_data, f)
                logger.info("写入数据成功,写入后数据为:
    {}".format(self.file_url, self.read_yaml()))
            except Exception as e:
                logger.error("{}写入数据失败,失败原因为:{}".format(self.file_url, e))
            return self.read_yaml()
    View Code

      

  • 相关阅读:
    C#自定义控件之数字文本框
    C# 校验字符串是否为IP格式
    C# winform 解决加载闪烁,背景透明等问题
    SQL Server 数据类型
    C#自定义控件之下拉列表框
    C#将 byte[ ] 转换为对应的struct
    AFNetworking图片上传
    xfs删除oracle数据文件恢复
    揭秘String类型背后的故事——带你领略汇编语言魅力
    [批处理]截取for命令里面的变量%%i
  • 原文地址:https://www.cnblogs.com/guo126/p/14324510.html
Copyright © 2011-2022 走看看