前言:目前公司的主要产品是一个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

 

  二、项目结构:

    

pytest自动发送测试报告邮件 pytest接口自动化_Code

    具体模块介绍:

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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# 重写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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# 循环拿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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# -*- 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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# 封装发送邮件方法
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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# 日志收集设置
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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

# 封装请求方法
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

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_02

pytest自动发送测试报告邮件 pytest接口自动化_发送邮件_03

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("读取{}数据成功,读取数据为:\n{}".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("写入数据成功,写入后数据为:\n{}".format(self.file_url, self.read_yaml()))
        except Exception as e:
            logger.error("{}写入数据失败,失败原因为:{}".format(self.file_url, e))
        return self.read_yaml()

View Code