前言:目前公司的主要产品是一个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("读取{}数据成功,读取数据为:\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