一、用到的知識點:
1. requests
2. pytest
3. Excel表格操作
4. 發郵件
5. 日志功能
6. 項目開發規范的目錄應用
7. allure
①. title知識點
②. description知識點
二、項目需求:
1. 從Excel中讀取記錄行(每一行就是一個API(url,請求類型,名稱,描述,參數,預期值))
2. 使用參數化對每一次的請求,要使用requests發請求,獲取請求結果,從結果中提取字段,跟預期值做斷言,
3. 使用allure生成測試報告
①. 為每一個請求用例添加title和description
4. 將測試報告發郵件
將allure報告文件夾打包成zip、發送zip文件
5. 關鍵點添加上log日志
①. 請求的時候
②. 斷言的時候
③. 可選打包的時候
④. 讀Excel的時候
6. 為了解耦合,需要遵循軟件開發規范
①. 數據文件夾
②. 配置文件夾
③. 腳本文件夾
三、項目分析:
首先考慮把pytest腳本跑起來,在這個基礎上,加功能
1. 加log日志
2. Excel表格操作,注意,需要把預期值的json串,loads成字典
3. requests操作,從Excel表格中,獲取到每一行數據,發請求
①. 請求的預期值有可能是多個,我們進行循環判斷
②. 請求的響應結果類型可能是多種,我們從response.headers["Content-Type"]去判斷,到底是什么類型的響應,然后進行反射
4. 執行終端命令:
①. subprocess
②. os.popen
5、壓縮文件、發送郵件、刪除report文件夾
四、項目完成:
1、配置ini文件
pytest.ini文件中不要有中文,哪怕是注釋的部分有中文也不行
[pytest]
addopts = -s -v -p no:warnings --alluredir ./report/allure_json
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
2、建立目錄結構
3、建立入口文件
import pytest
if __name__ == '__main__':
pytest.main()
4、配置文件settings(日志相關、Excel相關、Allure相關、郵件相關)
import os
import sys
import datetime
# 根目錄:
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# ---------------- 日志相關 --------------------
# 日志級別:
LOG_LEVEL = 'debug'
LOG_STREAM_LEVEL = 'debug' # 屏幕輸出流
LOG_FILE_LEVEL = 'info' # 文件輸出流
# 日志文件夾命名:
LOG_FILE_NAME = os.path.join(BASE_PATH, 'logs', datetime.datetime.now().strftime('%Y-%m-%d') + '.log')
# ---------------- Excel相關 --------------------
# Excel數據文件夾命名:
DATA_PATH = os.path.join(BASE_PATH, 'data')
# 切換data文件時,只需要更改文件名即可:
EXCEL_FILE_NAME = 'cnodejs接口.xlsx'
EXCEL_FILE_PATH = os.path.join(DATA_PATH, EXCEL_FILE_NAME)
if __name__ == '__main__':
# 添加頂級目錄:
sys.path.insert(0, BASE_PATH)
print(sys.path)
# ---------------- Allure相關 ----------------------
# 報告路徑:
REPORT_PATH = os.path.join(BASE_PATH, 'report')
# Allure的json文件夾命名:
ALLURE_JSON_DIR_NAME = 'allure_json'
# Allure的json文件路徑:
ALLURE_JSON_DIR_PATH = os.path.join(REPORT_PATH, ALLURE_JSON_DIR_NAME)
# allure的html文件夾命名:
ALLURE_REPORT_DIR_NAME = os.path.join(REPORT_PATH, 'allure_report')
# allure的html報告路徑
ALLURE_REPORT_DIR_PATH = os.path.join(REPORT_PATH, ALLURE_REPORT_DIR_NAME)
# 壓縮包路徑:
ZIP_FILE_PATH = os.path.join(REPORT_PATH, 'report.zip')
# 生成報告命令:
ALLURE_COMMAND = "allure generate {} -o {}".format(ALLURE_JSON_DIR_PATH, ALLURE_REPORT_DIR_PATH)
# ------------------- 郵件相關 -----------------
# 第三方 SMTP 服務:
MAIL_HOST = "smtp.qq.com"
MAIL_USERNAME = "xxxx@qq.com"
MAIL_TOKEN = "mpaocydzpzfjidge"
# 收件人,收件人可以有多個,以列表形式存放:
RECEIVERS = ['xxxx1@qq.com', 'xxxx2@qq.com']
# 郵件主題、收件人、發件人:
MAIL_SUBJECT = 'Python通過第三方發郵件'
# 可修改:設置收件人和發件人
SENDER = 'xxxx1@qq.com' # 發件人
# 郵件正文
MAIN_CONTENT = 'hi man:\r這是今日的{excel_file_name}的測試報告\r詳情下載附件\r查看方法,終端執行 allure open report\r測試人:{execute_tests_name},聯系電話:{execute_tests_phone} \r{execute_tests_date}'.format(
excel_file_name=EXCEL_FILE_NAME,
execute_tests_name="張達",
execute_tests_phone=1733813xxxx,
execute_tests_date=datetime.datetime.date(datetime.datetime.now())
)
5、測試用例test_case(輸出日志、動態加參、發郵件)
import pytest
import allure
# 引入配置文件:
from conf import settings
# 引入日志功能:
from util.LogHandler import logger
# 引用Excel表操作功能:
from util.ExcelHandler import ExcelHandler
# 引入發請求功能:
from util.RequestHhandler import RequestHandler
# 引入報告功能:
from util.AllureHandler import AllureHandler
# 引入發郵件功能:
from util.SendMailHandler import SendMailHandler
class TestCase(object):
@pytest.mark.parametrize("item", ExcelHandler().get_excel_data(settings.EXCEL_FILE_PATH))
def test_case(self, item):
# 調用日志功能的info級別:
# logger().info(item)
# 獲取每一行數並且發請求:
response = RequestHandler().get_response(item)
# 行為驅動標記:
allure.dynamic.feature(item['case_title'])
allure.dynamic.story(item['case_description'])
# 動態加參:
allure.dynamic.title(item['case_title'])
allure.dynamic.description(
"<b style='color:red;'>描述:</b>{}<br />"
"<b style='color:red;'>請求的url:</b>{}<br />"
"<b style='color:red;'>預期值:</b>{}<br />"
"<b style='color:red;'>實際執行結果:</b>{}<br />".format(
item['case_description'],
item['case_url'],
response[0],
response[1]
))
assert response[0] == response[1]
def teardown_class(self):
# 參數化用例都執行完畢才執行的操作:
logger().info('teardown_class')
# 執行allure命令,生成allure報告
AllureHandler().execute_command()
# 將測試報告打包並發送郵件:
SendMailHandler().send_mail_msg()
6、Allure功能(執行終端命令)
# 執行終端命令、subprocess要代替一些老舊的模塊命令:
from subprocess import Popen, call
from conf import settings
from util.LogHandler import logger
class AllureHandler(object):
# 讀取json文件,生成allure報告:
def execute_command(self):
# Python執行終端命令:shell=True:將['allure', 'generate', '-o', 'xxxx']替換為'allure generate -o'
try:
call(settings.ALLURE_COMMAND, shell=True)
logger().info('執行allure命令成功')
except Exception as e:
logger().error("執行allure命令失敗,詳情參考: {}".format(e))
7、日志功能(設置日志級別、格式化輸出日志)
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):
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')
8、Excel功能(for循環添加/列表解析)
import xlrd
from conf import settings
class ExcelHandler(object):
# Excel功能:
def __init__(self, excel_file_path=None):
self.excel_file_path = excel_file_path
def get_excel_data(self, excel_file_path):
# 讀取Excel表格:
book = xlrd.open_workbook(excel_file_path)
# 根據sheet名稱獲取sheet對象:
sheet = book.sheet_by_name('自動化測試')
# 獲取標題:
title = sheet.row_values(0)
# l = []
# 方式一循環添加返回列表:
# for row in range(1, sheet.nrows):
# l.append(dict(zip(title, sheet.row_values(row))))
# return l
# 方式二列表解析式返回:
return [dict(zip(title, sheet.row_values(row))) for row in range(1, sheet.nrows)]
def write_excel(self):
# 寫入Excel表格:
pass
if __name__ == '__main__':
ExcelHandler().get_excel_data(settings.EXCEL_FILE_PATH)
9、發請求功能(bs解析、定義私有方法、分不同的json返回類型處理)
import json
import requests
# 導入解析功能:
from bs4 import BeautifulSoup
# 引入日志功能:
from util.LogHandler import logger
class RequestHandler(object):
# 發請求功能:
def get_response(self, item):
# 獲得請求結果:
# logger().info(item)
return self._send_msg(item)
def _send_msg(self, item):
# 發請求的操作(私有的):
response = requests.request(
# 請求類型:
method=item['case_method'],
# 請求url:
url=item['case_url'],
# 處理請求中攜帶的data數據(私有的)
data=self._check_data_msg(item),
# 處理請求中的請求頭(私有的)
headers=self._check_headers_msg(item)
)
# 分不同的json返回類型處理:
Content_Type = response.headers['Content-Type'].split('/')[0]
# 反射:
if hasattr(self, '_check_{}_response'.format(Content_Type)):
obj = getattr(self, '_check_{}_response'.format(Content_Type))
res = obj(response, item)
else:
pass
return res
def _check_application_response(self, response, item):
# 處理json類型的響應:
response = response.json()
expect = json.loads(item['case_expect'])
for key, value in expect.items():
# 意味着預期值的字段跟實際請求結果的字段不一致:斷言失敗
if value != response.get(key, None):
logger().info('請求:{} 斷言失敗,預期值是:[{}] 實際執行結果:[{}], 相關參數:{}'.format(
item['case_url'],
value,
response.get(key, None),
item
))
return (value, response.get(key, None))
else:
# 斷言成功
return (value, response.get(key, None))
def _check_text_response(self, response, item):
# 處理文本類型的響應:
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
title = soup.find('title').text
return (title, item['case_expect'])
def _check_image_response(self, response, item):
# 處理jpeg圖片類型的響應:
pass
def _check_data_msg(self, item):
# 處理請求中攜帶的data數據(私有的):
if item.get('case_data', None):
# 如果請求中有data參數:
pass
else:
return {}
def _check_headers_msg(self, item):
# 處理請求中的請求頭(私有的)、預留接口負責處理,請求頭相關的邏輯
# 自定義一些固定的請求頭,也可以將Excel表格中的特殊請求頭更新到這個字典中
headers = {}
if item.get('case_headers', None):
headers.update(json.loads(item['case_headers']))
return headers
if __name__ == '__main__':
pass
10、發郵件功能(壓縮文件、發郵件配置、發郵件、自動刪除臨時文件)
import os
# 導入刪除文件夾/文件模塊:
import shutil
# 導入壓縮模塊:
import zipfile
# 導入日期模塊:
import datetime
# 導入發郵件模塊:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
# 導入配置文件:
from conf import settings
from util.LogHandler import logger
class SendMailHandler(object):
# 將測試報告壓縮並發送郵件
def _check_zip_file(self):
# 將測試報告壓縮:
base_dir = settings.ALLURE_REPORT_DIR_PATH
# 壓縮包路徑:
zip_file_path = settings.ZIP_FILE_PATH
f = zipfile.ZipFile(zip_file_path, '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_mail_msg(self):
# 調用壓縮功能:
self._check_zip_file()
# 調用發郵件操作:
self._send_mail()
# 調用刪除臨時文件:
self._del_temp_file()
def _send_mail(self):
# 發郵件操作:
# 第三方 SMTP 服務:
mail_host = settings.MAIL_HOST
# 下面兩個,可修改:
mail_user = settings.MAIL_USERNAME
mail_pass = settings.MAIL_TOKEN
# 可修改:設置收件人和發件人:
sender = settings.SENDER
# 收件人,收件人可以有多個,以列表形式存放:
receivers = settings.RECEIVERS
# 創建一個帶附件的實例對象:
message = MIMEMultipart()
# 可修改郵件主題、收件人、發件人:
subject = settings.MAIL_SUBJECT
# 下面三行不用修改:
message['Subject'] = Header(subject, 'utf-8')
# 發件人:
message['From'] = Header("{}".format(sender), 'utf-8')
# 收件人:
message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8')
# 郵件正文內容 html 形式郵件:相當於是一個報告預覽:
# 可修改:send_content
content = settings.MAIN_CONTENT
# 第一個參數為郵件正文內容:
html = MIMEText(_text=content, _subtype='plain', _charset='utf-8')
# 構造附件:
send_content = open(settings.ZIP_FILE_PATH, 'rb').read()
# 只允許修改第一個參數,后面兩個保持默認:
att = MIMEText(_text=send_content, _subtype='base64', _charset='utf-8')
# 不要改:
att["Content-Type"] = 'application/octet-stream'
# 頁面中,附件位置展示的附件名稱:
file_name = 'report.zip'
# 下面3行不要改、filename 為郵件附件中顯示什么名字:
att["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)
message.attach(html)
message.attach(att)
try:
smtp_obj = smtplib.SMTP()
smtp_obj.connect(mail_host, 25)
smtp_obj.login(mail_user, mail_pass)
smtp_obj.sendmail(sender, receivers, message.as_string())
smtp_obj.quit()
logger().info("郵件發送成功")
except smtplib.SMTPException as e:
logger().error("Error: 郵件發送失敗,詳情:{}".format(e))
def _del_temp_file(self):
# 清空report目錄:
logger().info('刪除臨時目錄')
shutil.rmtree(settings.REPORT_PATH)
logger().info('刪除臨時目錄成功')
if __name__ == '__main__':
# 在當前py文件測試:
SendMailHandler().send_mail_msg()
11、郵箱
12、報告展示
終端運行命令:allure open .\report\