前言:目前公司的主要产品是一个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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 重写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))
commonMethod.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 循环拿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
conf.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# -*- 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"))
emailSend.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 封装发送邮件方法 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))
log.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 日志收集设置 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() """
requests.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 封装请求方法 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
yamls.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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()