自动化测试脚本的编写

需求:

  1. 从Excel中读取记录行(每一行就是一个API(url,请求类型,名称,描述,参数,预期值))

  2. 使用参数化对每一次的请求,要使用requests发请求,获取请求结果,从结果中提取字段,跟预期值做断言,

  3. 使用allure生成测试报告

    1. 为每一个请求用例添加title和description

  4. 将测试报告发邮件

    1. 问题:发邮件的时候,能发文件夹吗?答案是不能,

    2. 解决办法是:将allure报告文件夹打包成zip

    3. 发送zip文件

  5. 在你认为的关键点添加上log日志

    1. 请求的时候

    2. 断言的时候

    3. 可选打包的时候

    4. 读Excel的时候

  6. 为了解耦合,需要遵循软件开发规范

    1. 数据文件夹

    2. 配置文件夹

    3. 脚本文件夹

    4. 等等

  7. 如果写的困难得,可以在一个文件中实现

用到的知识点:

  1. requests

  2. pytest

  3. Excel表格操作

  4. 发邮件

  5. 日志功能

  6. 项目开发规范的目录应用

  7. allure

    1. title知识点

      2.description知识点
一、1.把软件开发规范的相关目录建立起来:

二、配置settings:

import os
import datetime
import shutil
import sys

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# print(base_dir) #D:s27day69 b

# 定义文件名字:
file_name = "接口测试示例.xlsx"

# 路径拼接:
file_path = os.path.join(base_dir, "data", file_name)
# print(file_path) #D:s27day69 bdata接口测试示例.xlsx

# ---------------- 日志相关 --------------------
# 日志级别
LOG_LEVEL = 'debug'

# 屏幕输出流
LOG_STREAM_LEVEL = 'debug'

# 文件输出流
LOG_FILE_LEVEL = 'info'

# 日志文件命名
LOG_FILE_NAME = os.path.join(base_dir, 'logs', datetime.datetime.now().strftime('%Y-%m-%d') + '.log')

# allure报告相关:
report_path = os.path.join(base_dir, "report")
result_path = os.path.join(base_dir, "report", "result")
allure_html_path = os.path.join(base_dir, "report", "allure_html")
command = "allure generate {} -o {} --clean".format(result_path, allure_html_path)

# 要打包的根目录:
zip_case_result_path = allure_html_path

# 要打包的文件名:
zip_file_name = "allure_report.zip"

# 将打包文件存放哪去:
zip_save_path = report_path

# 邮箱相关:
# 第三方 SMTP 服务
# 设置服务器
mail_host = "smtp.qq.com"

# 用户名
mail_user = "xxxx@qq.com"

# 获取授权码
mail_pass = "mpaocydzpzfjidge"

# 发件人账号
sender = 'xxxx@qq.com'

# 接收邮件,可设置为你的QQ邮箱或者其他邮箱和多个收件人
receivers = ['1320685524@qq.com']

# 邮件主题:
subject = 'Python发送带附件的邮件示例'

# 邮件正文内容
send_content = '这是今天的测试用例报告,请下载附件并使用pycharm打开'

# 邮件附件路径:
send_file_path = os.path.join(zip_save_path, zip_file_name)
send_file_name = zip_file_name

if __name__ == '__main__':
# print(send_file_path)
pass
# os.remove(report_path)
# shutil.rmtree(report_path)
#查看路径:
# print(sys.path)
#查看包:
# print(sys.modules)

三、配置pytest.ini:

[pytest]
addopts = -s -v --alluredir ./report/result
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

四、data数据文件接口测试示例.xlsx:

五、logs日志文件:
六、report报告:
七、scripts测试用例:

import pytest
import allure
from utils.ExcelHandler import Excel
from utils.LogHandler import logger
from utils.RequestHandler import RequestHandler
from utils.AllureHandler import AllureHandler


@pytest.mark.parametrize("d", Excel().get_excel_data())
def test_case(d):
# print(d)
result = RequestHandler(d).get_response()
# logger().info(d)
allure.dynamic.title(d["case_project"])
allure.dynamic.description("<font color='red'>请求的URL:</font>{}<hr />"
"<font color='red'>请求的类型:</font>{}<hr />"
"<font color='red'>实际值:</font>{}<hr />"
"<font color='red'>预期值:</font>{}<hr />"
"".format(d["case_url"],
d["case_method"],
result[1],
result[0],
))
assert result[0] == result[1]

# def teardown_module():
# allure_obj = AllureHandler()
"""生成allure测试报告"""
# allure_obj.execute_command()
"""打包文件"""
# allure_obj.zip()
"""发送邮件"""
# allure_obj.send_email()

八、AllureHandler.py报告功能:

"""
执行allure命令生成报告
打包allure报告
发送压缩包
"""

import os
# 导入压缩文件用的模块
import zipfile
import smtplib
import shutil
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from conf import settings
from utils.LogHandler import logger
from subprocess import call, Popen


class AllureHandler(object):
def __init__(self):
"""清空report目录,以便后续方便操作"""
# shutil.rmtree(settings.report_path)
pass

def execute_command(self):
"""执行allure命令"""
# time.sleep(5)
logger().info("执行生成allure报告:{}".format(settings.command))
# os.system(settings.command)
# shell=True是可以识别字符串的命令:
call(settings.command, shell=True)

def zip(self):
"""打包allure报告"""
# 日志:
logger().info("打包文件名:{},打包到:{}".format(settings.zip_file_name, settings.zip_save_path))
# 要压缩文件夹的根路径并拼接:
base_dir = settings.zip_case_result_path
zip_file_name = settings.zip_file_name
# 保存打包文件的路径:
f = zipfile.ZipFile(os.path.join(settings.zip_save_path, zip_file_name), 'w', zipfile.ZIP_DEFLATED)
for dir_path, dir_name, file_names in os.walk(base_dir):
# 要是不replace,就从根目录开始复制
file_path = dir_path.replace(base_dir, '')
# 实现当前文件夹以及包含的所有文件
file_path = file_path and file_path + os.sep or ''
for file_name in file_names:
f.write(os.path.join(dir_path, file_name), file_path + file_name)
f.close()

def send_email(self):
"""将打包的allure文件发送给指定邮箱"""
logger().info("正在向{}发送邮件,请稍后.......".format(settings.receivers))
# 第三方SMTP服务
# 设置服务器
mail_host = settings.mail_host
# 用户名
mail_user = settings.mail_user
# 获取授权码
mail_pass = settings.mail_pass
# 发件人账号
sender = settings.sender
# 接收邮件,可设置为你的QQ邮箱或者其他邮箱和多个收件人
receivers = settings.receivers
# 创建一个带附件的实例
message = MIMEMultipart()

# 发件人
message['From'] = Header("我是发件人", 'utf-8')

# 收件人
message['To'] = Header("我是收件人", 'utf-8')

# 邮件主题
subject = settings.subject
message['Subject'] = Header(subject, 'utf-8')

# 邮件正文内容
send_content = settings.send_content
content_obj = MIMEText(send_content, 'plain', 'utf-8')
message.attach(content_obj)

# 构造附件1,发送当前目录下的文件
att1 = MIMEText(open(settings.send_file_path, 'rb').read(), "base64", "utf-8")

# 基于流的模式
att1["Content-Type"] = "application/octet-stream"

# 文件描述、filename是附件中显示的名字
att1["Content-Disposition"] = "attachment;filename = '{}'".format(settings.send_file_name)
message.attach(att1)

# 构造附件2,发送当前目录下的t2.py文件
# att2 = MIMEText(open("t2.py", "rb").read(), "base64", "utf-8")
#
# # 基于流的模式
# att2["Content-Type"] = "application/octet-stream"
#
# # 文件描述、filename是附件中显示的名字
# att2["Content-Disposition"] = "attachment;filename = 't2.py'"
# message.attach(att2)

try:
smtpObj = smtplib.SMTP()
# 25 为 SMTP 端口号
smtpObj.connect(mail_host, 25)
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
logger().info("向{}发送邮件成功".format(settings.receivers))
except smtplib.SMTPException as e:
logger().error("向{}发送邮件失败,可能的原因:{}".format(settings.receivers, e))

九、ExcelHandler.py文件表功能:

import xlrd
from conf import settings
from utils.LogHandler import logger

class Excel(object):
def get_excel_data(self):
"""获取excel表格数据"""
logger().info("读取Excel表格{} {}".format(settings.file_name,settings.file_path))
book = xlrd.open_workbook(filename=settings.file_path)
sheet = book.sheet_by_index(0)
# print(sheet.nrows) #8
title = sheet.row_values(0)
l = []
for row in range(1, sheet.nrows):
# print(sheet.row_values(row))
l.append(dict(zip(title, sheet.row_values(row))))
# print(l)
return l

if __name__ == '__main__':
Excel().get_excel_data()

十、LogHandler.py日志功能:

import logging
from conf import settings

class LoggerHandler:
""" 日志操作 """
_logger_level = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
def __init__(self, log_name, file_name, logger_level, stream_level='info', file_level='warning'):
self.log_name = log_name
self.file_name = file_name
self.logger_level = self._logger_level.get(logger_level, 'debug')
self.stream_level = self._logger_level.get(stream_level, 'info')
self.file_level = self._logger_level.get(file_level, 'warning')
# 创建日志对象
self.logger = logging.getLogger(self.log_name)
# 设置日志级别
self.logger.setLevel(self.logger_level)
if not self.logger.handlers:
# 设置日志输出流
f_stream = logging.StreamHandler()
f_file = logging.FileHandler(self.file_name)
# 设置输出流级别
f_stream.setLevel(self.stream_level)
f_file.setLevel(self.file_level)
# 设置日志输出格式
formatter = logging.Formatter(
"%(asctime)s %(name)s %(levelname)s %(message)s"
)
f_stream.setFormatter(formatter)
f_file.setFormatter(formatter)
self.logger.addHandler(f_stream)
self.logger.addHandler(f_file)
@property
def get_logger(self):
"""伪装成属性返回logger对象"""
return self.logger

def logger(log_name='接口测试'):
return LoggerHandler(
log_name=log_name,
logger_level=settings.LOG_LEVEL,
file_name=settings.LOG_FILE_NAME,
stream_level=settings.LOG_STREAM_LEVEL,
file_level=settings.LOG_FILE_LEVEL
).get_logger

if __name__ == '__main__':
logger().debug('aaaa')
logger().info('aaaa')
logger().warning('aaaa')

十一、RequestHandler.py请求功能:

import requests
import json
from utils.LogHandler import logger
from bs4 import BeautifulSoup


class RequestHandler(object):
def __init__(self, d):
self.d = d

def get_response(self):
"""获取请求结果"""
return self.send_msg()

def _response_application(self, response):
"""校验json类型的返回"""
response = response.json()
expect = json.loads(self.d.get("case_expect"))
for k in expect:
if expect[k] != response.get(k, "没有这个key:{}".format(k)):
return {k: expect[k]}, {k: response.get(k, "没有这个key:{}".format(k))}
return {k: expect[k]}, {k: response.get(k, "没有这个key:{}".format(k))}

def _response_text(self, response):
"""校验文本类型的返回"""
# response.title()
soup = BeautifulSoup(response.text, "html.parser")
title = soup.find(name="title").text
# print(title.text)
logger().info("文本类型的请求结果,预期值:{} | 实际值:{}".format(self.d.get("case_expect"), title))
return title, self.d.get("case_expect")

def send_msg(self):
"""发请求"""
logger().info("请求URL:{},类型:{}".format(self.d.get("case_url"), self.d.get("case_method")))
response = requests.request(
method=self.d.get("case_method"),
url=self.d.get("case_url"),
# params=self._check_params(),
# data=self._check_data()
)
header = response.headers["Content-Type"].split("/", 1)[0]
# header = _response_application
if hasattr(self, "_response_{}".format(header)):
a = getattr(self, "_response_{}".format(header))
result = a(response)
logger().info("预期值:{} 实际值:{}".format(result[0], result[1]))
return result

def _check_params(self):
"""检查请求参数"""
params = self.d.get("case_params")
if params:
return {}
else:
return {}

def _check_data(self):
""" 检查请求参数 """
params = self.d.get("case_params")
if params:
return {}
else:
return {}

if __name__ == '__main__':
# r1 = requests.get("https://www.cnblogs.com/Neeo/articles/10951734.html")
# print(r1.text)
# print(r1.headers)
# print(r1.title)
pass
# r2 = requests.get("https://www.v2ex.com/api/site/info.json")
# print(r2.headers)

# r3 = requests.post("https://cnodejs.org/api/v1/message/mark_all")
# print(r3.headers)

十二、run.py程序执行入口:

import shutil
import pytest
from utils.AllureHandler import AllureHandler
from conf import settings

if __name__ == '__main__':
pytest.main()
# shutil.rmtree(r"D:s27day69包含log日志功能 b eport")
a = AllureHandler()
a.execute_command()
a.zip()
a.send_email()

十三、效果展示:

原文地址:https://www.cnblogs.com/zhang-da/p/12305082.html