接口自动化
接口自动化的框架开发:
-
用到的知识点:
-
pytest
-
allure
-
参数化
-
Excel操作,不会,用xlrd
-
日志操作,学过,不太会
-
邮件,会
-
文件操作,文件压缩, 没讲,但你要会的,zipfile
-
执行终端命令,os.system, subprocess:cell, popen
-
如何使用python查看当前目录下的所有文件或者目录?
-
-
-
实现的个功能:
-
将各个功能拆分为多个目录
-
使用参数化读取Excel中的用例
-
发请求
-
获取请求结果
-
校验/断言
-
-
使用allure生成测试报告
-
将allure测试报告所在的目录打包
-
将打包的zip文件使用邮件发送到1206180814@qq.com
-
在重点位置加日志
-
实现思路:
-
读取Excel,每一行数据都是一个用例,你在读出来之后,把这个一行用例封装成一个对象,字典,列表。
-
使用参数化每次讲一个用例对象传进去。
-
使用requests获取用例对象中的相关参数发请求。
-
然后将请求结果与预期值(用例对象)做断言
-
此时,allure所需的json数据已经有了。
-
使用allure命名读取josn数据生成测试报告
-
将报告压缩
-
使用发邮件功能将压缩文件发送
-
在重点位置,添加日志功能
接口自动化
本质:通过requests和Excel(数据库)对用例进行批量的自动化回归测试。
关于Excel
操作Excel:https://www.cnblogs.com/Neeo/articles/11650149.html

import xlrd from conf import settings book = xlrd.open_workbook(settings.FILE_PATH) sheet = book.sheet_by_index(0) # book.sheet_by_name('通用接口') # print(sheet.nrows, sheet.ncols) # 获取所有行和列数 # 获取指定行 # print(sheet.row_values(0)) # print(sheet.row_values(1)) # 获取指定列 # print(sheet.col_values(0)) l = [] title = sheet.row_values(0) print(title) for row in range(1, sheet.nrows): l.append(dict(zip(title, sheet.row_values(row)))) print(l)
单元测试框架
pytest 目录结构创建
操作excel表格
settings.py:关于Ecel配置

import os import datetime BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # -------------------- 关于Ecel配置------------- # data的excel的路径 FILE_NAME = "接口测试示例-2.xlsx" FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME)
utils/ExcelHandler.py:操作excel

""" 处理Excel """ import xlrd from conf import settings from utils.LogHandler import logger class ExcelOperate(object): def __init__(self, file_path, sheet_by_index=0): self.file_path = file_path # excel操作拿到sheet的索引默认0 self.sheet_by_index = sheet_by_index # 打开excel路径 book = xlrd.open_workbook(self.file_path) self.sheet = book.sheet_by_index(self.sheet_by_index) def get_excel(self): """ 获取Excel数据 """ # l = [] # 取第一行的title title = self.sheet.row_values(0) # print(title) # 拿到所有的行和第一行的title对应上 # for row in range(1, self.sheet.nrows): # 通过拉链处理excel数据一条数据是一个个字典放到列表里 # l.append(dict(zip(title, self.sheet.row_values(row)))) # return l # print(1111, self.sheet.nrows) l = [dict(zip(title, self.sheet.row_values(row))) for row in range(1, self.sheet.nrows)] logger().info('读取Excel 成功,数据已返回') return l if __name__ == '__main__': excel_data_list = ExcelOperate(settings.FILE_PATH, 2).get_excel() print(excel_data_list)
test_case.py:

import os import shutil import pytest import allure from deepdiff import DeepDiff from utils.ExcelHandler import ExcelOperate from utils.RequestsHandler import RequestsOperate from utils.LogHandler import logger from conf import settings excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel() class TestCase(object): @pytest.mark.parametrize('item', excel_data_list) def test_case(self, item): logger().info('正在进行断言.....') except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(DeepDiff(except_date, result)) allure.dynamic.title(item['title']) allure.dynamic.description( "<b style='color:red'>请求的url:</b>{}<hr />" "<b style='color:red'>预期值: </b>{}<hr />" "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result) ) assert not DeepDiff(except_date, result).get('values_changed', None) logger().info('完成断言,{}'.format(except_date, result))
如何处理数据依赖
依赖:当条测试用例要完成的话需要上面的一条测试用例为基础(比如cookie 依赖于上面测试成的cookie)
在Excel中编写测试用例的时候:(token依赖于第一条测试用例data的数据)
-
被依赖的接口用例写在上面
-
对于依赖的字段设置规则:(case_001>request_data>token 依赖于case_001用例的request_data字段的token的键)
-
{"user":"admin", "token":"xxxxadsasdads"}
-
{"token":"${case_001>request_data>token}$"}
-
有依赖数据处理:
start.py:

import os import shutil import pytest from conf.settings import BASE_DIR from utils.AllureHandler import AllureOperate if __name__ == '__main__': # 更改工作目录 os.chdir(BASE_DIR) # 执行用例 pytest.main()
test_case.py:

import os import shutil import pytest import allure from deepdiff import DeepDiff from utils.ExcelHandler import ExcelOperate from utils.RequestsHandler import RequestsOperate from utils.LogHandler import logger from conf import settings # 3是excel的sheet的索引 excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel() class TestCase(object): @pytest.mark.parametrize('item', excel_data_list) def test_case(self, item): logger().info('正在进行断言.....') except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(DeepDiff(except_date, result))
RequestsHandler.py:

import re import json import requests from jsonpath_rw import parse from urllib.parse import urlparse from conf import settings from utils.LogHandler import logger # --------------- 有数cookies依赖的写法-2 --------------------- class RequestsOperate(object): def __init__(self, current_case, all_excel_data_list): self.current_case = current_case self.all_excel_data_list = all_excel_data_list def get_response_msg(self): """ 发送请求并且获取结果 """ return self._send_msg() def _send_msg(self): """ 发请求 """ # print(1111111111, self.current_case) logger().info('正在向 {} 发送请求,{}'.format(self.current_case['url'], self.current_case)) response = requests.request( method=self.current_case['method'], url=self.current_case['url'], data=self._check__request_data(), params=self._check__request_params(), cookies=self._check_request_cookies(), headers=self._check_request_headers(), ) # print("------------- {} 请求结果 ----------------".format(self.current_case['case_num']), response.json()) self._write_cookies(response) # print(111111, "预期值", self.current_case['except'], '实际执行结果', response.json()) return json.loads(self.current_case['except']), response.json() def _check_request_headers(self): """ 校验请求头 做携带cookis 和 数据依赖的问题 """ headers = self.current_case.get("headers", None) if headers: return self._operate_re_msg(headers) else: return {} def _write_cookies(self, response): """ 监测响应结果中是否含有cookies,有就保存起来 """ cookies = response.cookies.get_dict() # print(self.current_case) for item in self.all_excel_data_list: if item['case_num'] == self.current_case['case_num']: # print(111111111, item) # 新加一个key再item中 item['temporary_response_cookies'] = response.cookies.get_dict() if response.headers['Content-Type'].lower() == 'application/json;charset=utf-8': item['temporary_response_json'] = response.json() item['temporary_request_headers'] = self.current_case['headers'] item['temporary_request_data'] = self.current_case['data'] item['temporary_request_json'] = self.current_case['json'] item['temporary_request_params'] = self.current_case['params'] item['temporary_response_headers'] = response.headers # print(self.all_excel_data_list) def _check__request_data(self): """ 处理 请求的 data 参数,检查是否有依赖 """ data = self.current_case['data'] if data: # print(data, type(data)) return json.loads(data) else: return {} def _check__request_params(self): """ 处理 请求的 params 参数,检查是否有依赖 """ params = self.current_case['params'] if params: return json.loads(params) else: return {} def _check_request_cookies(self): """ 处理请求中的 cookies """ cookies_case_num = self.current_case.get("cookies", None) # print(1111111111, cookies_case_num) if cookies_case_num: # 当前接口需要cookies # print(2222222222, cookies_case_num) for item in self.all_excel_data_list: if item['case_num'] == cookies_case_num: return item.get('temporary_response_cookies', {}) else: return {} def _operate_re_msg(self, parameter): """ 正则校验,数据依赖的字段 :param parameter: 各种参数,如: data,headers, params :return: """ # print(22222222,parameter) # 使用 re 提取依赖字段 {"testfan-token": "${neeo_001>response_json>data}$"} if isinstance(parameter, dict): parameter = json.dumps(parameter) # re只能提取字符串 \${(.*?)}\$ 匹配:${neeo_001>response_json>data}$"} pattern = re.compile('\${(.*?)}\$') # 找到匹配好的数据据 rule_list = pattern.findall(parameter) # print(333333333, parameter) # ["neeo_001>response_json>data"] if rule_list: # 该参数有数据依赖要处理 for rule in rule_list: case_num, params, json_path = rule.split(">") # print(333333333, case_num, params, json_path) # neeo_001 response_json data.info.phone for line in self.all_excel_data_list: if line['case_num'] == case_num: # print(11111111111, self.current_case, '\n', params, '\n', line) temp_data = line["temporary_{}".format(params)] # print(temp_data, type(temp_data)) # 判断temp_data是不是字符串 if isinstance(temp_data, str): temp_data = json.loads(temp_data) match_list = parse(json_path).find(temp_data) # print(333333333, match_list) # 如果match_list不为空 if match_list: # 提取出用例1的data match_data = [v.value for v in match_list][0] # print(match_data) # 将提取出来的值替换到原来规则 24fcbcc6dc004f3eb9ef13f59160ca7c --> {"testfan-token": "${neeo_001>response_json>data}$"} parameter = re.sub(pattern=pattern, repl=match_data, string=parameter, count=1) return json.loads(parameter) else: # 该参数有数据 但没有数据依赖要处理 正常返回参数就行 if isinstance(parameter, str): parameter = json.loads(parameter) return parameter
re.sub():匹配替换为选择的文本
从源码中看出re.sub()函数共有5个参数:
pattern:表示正则中的模式字符串;
repl:表示要替换的字符串(即匹配到pattern后替换为repl),也可以是个函数;
string:表示要被处理(查找替换)的原始字符串;
count:可选参数,表示要替换的最大次数,而且必须是非负整数,该参数默认为0,即所有的匹配都会替换;
flags:可选参数,表示编译时用的匹配模式(如忽略大小写、多行模式等),数字形式,默认为0。
没有依赖处理:
test_case.py:

import os import shutil import pytest import allure from deepdiff import DeepDiff from utils.ExcelHandler import ExcelOperate from utils.RequestsHandler import RequestsOperate from utils.LogHandler import logger from conf import settings excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel() class TestCase(object): # @classmethod # def setup_class(cls): # cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel() @pytest.mark.parametrize('item', excel_data_list) def test_case(self, item): logger().info('正在进行断言.....') # 请求数据 读取excel except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(DeepDiff(except_date, result)) allure.dynamic.title(item['title']) allure.dynamic.description( "<b style='color:red'>请求的url:</b>{}<hr />" "<b style='color:red'>预期值: </b>{}<hr />" "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result) ) assert not DeepDiff(except_date, result).get('values_changed', None) logger().info('完成断言,{}'.format(except_date, result))
RequestsHandler.py:

# --------------- 没有依赖的写法 ------------------------------- # class RequestsOperate(object): # # def __init__(self, current_case): # 当前的case # self.current_case = current_case # # def get_response_msg(self): # """ 发送请求并且获取结果 """ # self._send_msg() # # # def _send_msg(self): # """ 发请求 """ # response = requests.request( # method=self.current_case['method'], # url=self.current_case['url'], # data=self._check__request_data(), # params=self._check__request_params(), # ) # print("------------- 请求结果 ----------------", response.json(), '\n', "------------- 请求结果 ----------------") # # def _check__request_data(self): # """ 处理 请求的 data 参数,检查是否有依赖 """ # data = self.current_case['data'] # if data: # # print(data, type(data)) # return json.loads(data) # else: # return {} # # def _check__request_params(self): # """ 处理 请求的 params 参数,检查是否有依赖 """ # params = self.current_case['params'] # if params: # return json.loads(params) # else: # return {} if __name__ == '__main__': from utils.ExcelHandler import ExcelOperate from conf import settings # 获取excel excel_data_list = ExcelOperate(settings.FILE_PATH, sheet_by_index=3).get_excel() # 读每条测试用例 for item in excel_data_list: RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(item)
数据依赖有cookies的处理
关于cookies处理
第一种
借鉴postman的cookies管理器思路,即每个请求都监测响应结果是否有cookies返回,如果有,就保存,以域名的形式保存该cookies
如果有同域名的请求,就自动在headers中携带该cookies
settings.py:

import os import datetime BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # -------------------- 关于Ecel配置------------- FILE_NAME = "接口测试示例-2.xlsx" FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME) # --------------------- template cookies dict --------------- COOKIES_DICT = {}
RequestsHandler:

# --------------- 有数cookies依赖的写法-1 --------------------- # class RequestsOperate(object): # # def __init__(self, current_case): # self.current_case = current_case # # def get_response_msg(self): # """ 发送请求并且获取结果 """ # self._send_msg() # # def _send_msg(self): # """ 发请求 """ # response = requests.request( # method=self.current_case['method'], # url=self.current_case['url'], # data=self._check__request_data(), # params=self._check__request_params(), # cookies=self._check_request_cookies(), # ) # print("------------- 请求结果 ----------------", response.json(), response.cookies.get_dict(), response.headers, '\n', "------------- 请求结果 ----------------") # self._write_cookies(response) # # # def _write_cookies(self,response): # """ 监测响应结果中是否含有cookies,有就保存起来 """ # cookies = response.cookies.get_dict() # if cookies: # 拿到域名 # netloc = urlparse(url=response.url).netloc # settings.COOKIES_DICT[netloc] = cookies # # print(2222222222, settings.COOKIES_DICT) # else: # pass # # def _check__request_data(self): # """ 处理 请求的 data 参数,检查是否有依赖 """ # data = self.current_case['data'] # if data: # # print(data, type(data)) # return json.loads(data) # else: # return {} # # def _check__request_params(self): # """ 处理 请求的 params 参数,检查是否有依赖 """ # params = self.current_case['params'] # if params: # return json.loads(params) # else: # return {} # # def _check_request_cookies(self): # """ 处理请求中的 cookies (依赖)""" # if self.current_case['cookies']: # # 获取当前的url的域名 # netloc = urlparse(url=self.current_case['url']).netloc # # 根据域名从配置文件中取该域名下的cookies # cookies = settings.COOKIES_DICT.get(netloc, None) # if cookies: # 如果有cookies要携带 # return cookies # else: # return {} # else: # return {}
urlparse()解析url的方法十分简便,在写爬虫时非常有用,值得学习。
python3中urlparse模块和urllib模块合并,urlparse()在urllib.parse中进行调用。
urlparse()把url拆分为6个部分,scheme(协议),netloc(域名),path(路径),params(可选参数),query(连接键值对),fragment(特殊锚),并且以元组形式返回。
urlparse()具体怎么分析url呢,看看下面这个例子你就能明白了。
from urllib.parse import urlparse url = 'https://github.com/search?q=krislp' parse = urlparse(url) print(parse)
输出结果:
ParseResult(scheme='https', netloc='github.com', path='/search', params='', query='q=krislp', fragment='')
第二种
我们在请求中,判断响应结果中是否返回了cookies,如果返回了,就保存到当前的用例对象中,该对象就是一个大的字典。将cookies和值保存为一个key value 谁要用,就来拿该参数

import re import json import requests from jsonpath_rw import parse from utils.LogHandler import logger # --------------- 有数cookies依赖的写法-2 --------------------- class RequestsOperate(object): def __init__(self, current_case, all_excel_data_list): self.current_case = current_case self.all_excel_data_list = all_excel_data_list def get_response_msg(self): """ 发送请求并且获取结果 """ return self._send_msg() def _send_msg(self): """ 发请求 """ # print(1111111111, self.current_case) logger().info('正在向 {} 发送请求,{}'.format(self.current_case['url'], self.current_case)) response = requests.request( method=self.current_case['method'], url=self.current_case['url'], data=self._check__request_data(), params=self._check__request_params(), cookies=self._check_request_cookies(), headers=self._check_request_headers(), ) # print("------------- {} 请求结果 ----------------".format(self.current_case['case_num']), response.json()) self._write_cookies(response) # print(111111, "预期值", self.current_case['except'], '实际执行结果', response.json()) return json.loads(self.current_case['except']), response.json() def _check_request_headers(self): """ 校验请求头 做携带cookis 和 数据依赖的问题 """ headers = self.current_case.get("headers", None) if headers: return self._operate_re_msg(headers) else: return {} def _write_cookies(self, response): """ 监测响应结果中是否含有cookies,有就保存起来 """ cookies = response.cookies.get_dict() # print(self.current_case) for item in self.all_excel_data_list: if item['case_num'] == self.current_case['case_num']: # print(111111111, item) # 新加一个key再item中 item['temporary_response_cookies'] = response.cookies.get_dict() if response.headers['Content-Type'].lower() == 'application/json;charset=utf-8': item['temporary_response_json'] = response.json() item['temporary_request_headers'] = self.current_case['headers'] item['temporary_request_data'] = self.current_case['data'] item['temporary_request_json'] = self.current_case['json'] item['temporary_request_params'] = self.current_case['params'] item['temporary_response_headers'] = response.headers # print(self.all_excel_data_list) def _check_request_cookies(self): """ 处理请求中的 cookies 是否有数据依赖""" cookies_case_num = self.current_case.get("cookies", None) # print(1111111111, cookies_case_num) if cookies_case_num: # 当前接口需要cookies # print(2222222222, cookies_case_num) for item in self.all_excel_data_list: if item['case_num'] == cookies_case_num: return item.get('temporary_response_cookies', {}) else: return {}
Deepdiff模块:
https://www.cnblogs.com/Neeo/articles/12785493.html
DeepDiff模块常用来校验两个对象是否一致,并找出其中差异之处,它提供了:
-
DeepDiff:字典,可迭代项,字符串和其他对象的深层差异。它将递归地查找所有更改。
-
DeepSearch:在其他对象中搜索对象。
-
DeepHash:根据对象的内容对其进行哈希处理。

from deepdiff import DeepDiff data = {'code': '1', 'message': 'success'} response_json = {'code': '0', 'data': 'c03d4a1ef6d34ccdbebbe1b6fd72ca31'} d1 = {"a": 1, "b": 2, "c": 3, 'e': {"a": 2}} d2 = {"a": 1, "b": 2, "c": 3, "d": 4, 'e': {"a": 1}} # print(DeepDiff(d1, d2)) print(DeepDiff(data, response_json))
结果:{'dictionary_item_added': [root['data']], 'dictionary_item_removed': [root['message']], 'values_changed': {"root['code']": {'new_value': '0', 'old_value': '1'}}}
断言处理test_case.py:

import os import shutil import pytest from deepdiff import DeepDiff from utils.ExcelHandler import ExcelOperate from utils.RequestsHandler import RequestsOperate from utils.LogHandler import logger from conf import settings excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel() class TestCase(object): # @classmethod # def setup_class(cls): # cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel() @pytest.mark.parametrize('item', excel_data_list) def test_case(self, item): logger().info('正在进行断言.....') except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(DeepDiff(except_date, result)) # 断言处理 # get 如果能取到values_changed就返回,娶不到就返回none assert not DeepDiff(except_date, result).get('values_changed', None) logger().info('完成断言,{}'.format(except_date, result))
alluredir
python库:https://pypi.org/
下载alluredir库:
pip install -i https://pypi.doubanio.com/simple allure-pytest
pytest.ini:

[pytest] addopts = -s -v --alluredir ./report/json_result testpaths = ./scripts python_files = test_*.py python_classes = Test* python_functions = test_*
生成报告之前删除之前的json文件(在用例执行前)start.py:

import os import shutil import pytest from conf.settings import BASE_DIR from utils.AllureHandler import AllureOperate if __name__ == '__main__': # 删除之前的json文件(在用例执行前) dir_path = os.path.join(BASE_DIR, 'report', 'json_result') shutil.rmtree(dir_path) # 更改工作目录 os.chdir(BASE_DIR) # 执行用例 pytest.main()
关于urllib
取域名用
参考:https://www.cnblogs.com/Neeo/articles/11520952.html
如何删除一个非空目录
用shuitil

import shutil ''' shutil模块对文件和文件集合提供了许多高级操作。特别是,提供了支持文件复制和删除的功能 ''' # shutil.copy(src, dst) # 拷贝文件 # shutil.move(src, dst) # 移动目录或者文件 # shutil.rmtree(path) # 递归删除目录,无法直接删除文件 # shutil.make_archive(base_name, format('zip')) # 将目录或者文件以指定格式压缩 # shutil.unpack_archive(filename, extract_dir) # 解压缩 # see also: https://docs.python.org/3/library/shutil.html
关于生成测试报告的命令
参考:https://docs.python.org/zh-cn/3/library/subprocess.html
settings.py:

import os import datetime BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # -------------------- 关于Ecel配置------------- FILE_NAME = "接口测试示例-2.xlsx" FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME) # --------------------- template cookies dict --------------- COOKIES_DICT = {} # -------------------- allure 报告相关 ------------ ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format( from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'), save_to_path=os.path.join(BASE_DIR, 'report', "allure_result") )
utils/AllureHandler.py:

from utils.LogHandler import logger from conf import settings class AllureOperate(object): def get_allure_report(self): """ 生成报告 """ logger().info('正在生成测试报告.....') # 字符串命令ALLURE_COMMAND,此命令要像字符串shell一样执行 shell=True subprocess.call(settings.ALLURE_COMMAND, shell=True) logger().info('生成测试报告成功.....') ''' ["allure", "generate", ] '''
start.py:

import os import shutil import pytest from conf.settings import BASE_DIR from utils.AllureHandler import AllureOperate if __name__ == '__main__': # 删除之前的json文件(再用例执行前) dir_path = os.path.join(BASE_DIR, 'report', 'json_result') shutil.rmtree(dir_path) # 更改工作目录 os.chdir(BASE_DIR) # 执行用例 pytest.main() # 生成allure报告 allure_obj = AllureOperate() allure_obj.get_allure_report()
关于打包
zipfile
参考:https://www.cnblogs.com/Neeo/articles/11934072.html
AllureHandler.py:

""" allure报告相关 """ import os import subprocess import zipfile import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from utils.LogHandler import logger from conf import settings class AllureOperate(object): def get_allure_report(self): """ 生成报告 """ # os.system(ALLURE_COMMAND) # subprocess.Popen(ALLURE_COMMAND, shell=True) logger().info('正在生成测试报告.....') subprocess.call(settings.ALLURE_COMMAND, shell=True) logger().info('生成测试报告成功.....') ''' ["allure", "generate", ] ''' def check_zip(self): """ 打包 """ try: logger().info('正在打包测试报告.....') # BASE_DIR:当前脚本的父级目录 BASE_DIR = os.path.join(settings.BASE_DIR, 'report') start_zip_dir = os.path.join(BASE_DIR, 'allure_result') # 要压缩文件夹的根路径 zip_file_name = 'allure_report.zip' # 为压缩后的文件起个名字 # 下面是固定写法 zip_file_path = os.path.join(BASE_DIR, zip_file_name) f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) for dir_path, dir_name, file_names in os.walk(start_zip_dir): # 要是不replace,就从根目录开始复制 file_path = dir_path.replace(start_zip_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() logger().info('打包测试报告完成.....') except Exception as e: logger().error('打包测试报告失败: {}'.format(e)) if __name__ == '__main__': AllureOperate().get_allure_report()
start.py:

import os import shutil import pytest from conf.settings import BASE_DIR from utils.AllureHandler import AllureOperate if __name__ == '__main__': # 删除之前的json文件(再用例执行前) dir_path = os.path.join(BASE_DIR, 'report', 'json_result') shutil.rmtree(dir_path) # 更改工作目录 os.chdir(BASE_DIR) # 执行用例 pytest.main() # 生成allure报告 allure_obj = AllureOperate() allure_obj.get_allure_report() # 压缩文件 allure_obj.check_zip()
发邮件
AllureHandler.py:

""" allure报告相关 """ import os import subprocess import zipfile import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from utils.LogHandler import logger from conf import settings class AllureOperate(object): def get_allure_report(self): """ 生成报告 """ # os.system(ALLURE_COMMAND) # subprocess.Popen(ALLURE_COMMAND, shell=True) logger().info('正在生成测试报告.....') subprocess.call(settings.ALLURE_COMMAND, shell=True) logger().info('生成测试报告成功.....') ''' ["allure", "generate", ] ''' def check_zip(self): """ 打包 """ try: logger().info('正在打包测试报告.....') # BASE_DIR:当前脚本的父级目录 BASE_DIR = os.path.join(settings.BASE_DIR, 'report') start_zip_dir = os.path.join(BASE_DIR, 'allure_result') # 要压缩文件夹的根路径 zip_file_name = 'allure_report.zip' # 为压缩后的文件起个名字 # 下面是固定写法 zip_file_path = os.path.join(BASE_DIR, zip_file_name) f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) for dir_path, dir_name, file_names in os.walk(start_zip_dir): # 要是不replace,就从根目录开始复制 file_path = dir_path.replace(start_zip_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() logger().info('打包测试报告完成.....') except Exception as e: logger().error('打包测试报告失败: {}'.format(e)) def send_mail(self): # 第三方 SMTP 服务 mail_host = settings.MAIL_HOST # 设置服务器 # 勿动 mail_user = settings.MAIL_USER # 用户名 mail_pass = settings.MAIL_TOKEN # 口令 # 设置收件人和发件人 sender = settings.SENDER receivers = settings.RECEIVERS # 创建一个带附件的实例对象 message = MIMEMultipart() # 邮件主题、收件人、发件人 subject = settings.THEME # 邮件主题 message['Subject'] = Header(subject, 'utf-8') message['From'] = Header("{}".format(sender), 'utf-8') # 发件人 message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8') # 收件人 # 邮件正文内容 send_content = settings.SEND_CONTENT content_obj = MIMEText(send_content, 'plain', 'utf-8') # 第一个参数为邮件内容 message.attach(content_obj) # 构造附件 att = MIMEText(_text=self._get_zip_file(), _subtype='base64', _charset='utf-8') att["Content-Type"] = 'application/octet-stream' att["Content-Disposition"] = 'attachment; filename="{}"'.format(settings.SEND_FILE_NAME ) # # filename 为邮件附件中显示什么名字 message.attach(att) try: smtp_obj = smtplib.SMTP() smtp_obj.connect(mail_host, 25) # 25 为 SMTP 端口号 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('email send error: {}'.format(e)) def _get_zip_file(self): """ 获取zip文件内容 """ with open(file=os.path.join(settings.BASE_DIR, 'report', 'allure_report.zip'), mode='rb') as f: return f.read() if __name__ == '__main__': AllureOperate().get_allure_report()
settings.py:

import os import datetime BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # -------------------- 关于Ecel配置------------- FILE_NAME = "接口测试示例-2.xlsx" FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME) # --------------------- template cookies dict --------------- COOKIES_DICT = {} # -------------------- allure 报告相关 ------------ ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format( from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'), save_to_path=os.path.join(BASE_DIR, 'report', "allure_result") ) # ------------------------ 邮件相关 ------------- MAIL_HOST = "smtp.qq.com" # 设置服务器 # 勿动 MAIL_USER = "你的QQ邮箱@qq.com" # 用户名 MAIL_TOKEN = "你的授权码" # 授权码 # 设置收件人和发件人 SENDER = 'xxxx@qq.com' RECEIVERS = ['xxxxx@qq.com', 'xxxxxx@163.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 # 邮件主题 THEME = '请查阅--s28的第一个测试报告' # 正文内容 SEND_CONTENT = 'hi man,你收到附件了吗?' # 附件的file name SEND_FILE_NAME = 'allure_report.zip'
start.py:

import os import shutil import pytest from conf.settings import BASE_DIR from utils.AllureHandler import AllureOperate if __name__ == '__main__': # 删除之前的json文件(再用例执行前) dir_path = os.path.join(BASE_DIR, 'report', 'json_result') shutil.rmtree(dir_path) # 更改工作目录 os.chdir(BASE_DIR) # 执行用例 pytest.main() # 生成allure报告 allure_obj = AllureOperate() allure_obj.get_allure_report() # 压缩文件 allure_obj.check_zip() # 发邮件 allure_obj.send_mail()
日志
参考:https://www.cnblogs.com/Neeo/articles/10951734.html#%E7%A4%BA%E4%BE%8B
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) # print(11111111111, self.logger.handlers) @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') logger().error('bbbbbbbb')
settings.py

import os import datetime BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # -------------------- 关于Ecel配置------------- FILE_NAME = "接口测试示例-2.xlsx" FILE_PATH = os.path.join(BASE_DIR, 'data', FILE_NAME) # --------------------- template cookies dict --------------- COOKIES_DICT = {} # -------------------- allure 报告相关 ------------ ALLURE_COMMAND = 'allure generate {from_json_path} -o {save_to_path} --clean'.format( from_json_path=os.path.join(BASE_DIR, 'report', 'json_result'), save_to_path=os.path.join(BASE_DIR, 'report', "allure_result") ) # ------------------------ 邮件相关 ------------- MAIL_HOST = "smtp.qq.com" # 设置服务器 # 勿动 MAIL_USER = "你的QQ邮箱@qq.com" # 用户名 MAIL_TOKEN = "你的授权码" # 授权码 # 设置收件人和发件人 SENDER = 'xxxx@qq.com' RECEIVERS = ['xxxxx@qq.com', 'xxxxxx@163.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 # 邮件主题 THEME = '请查阅--s28的第一个测试报告' # 正文内容 SEND_CONTENT = 'hi man,你收到附件了吗?' # 附件的file name SEND_FILE_NAME = 'allure_report.zip' # ---------------- 日志相关 -------------------- # 日志级别 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') if __name__ == '__main__': print(LOG_FILE_NAME)
使用:

""" allure报告相关 """ import os import subprocess import zipfile import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from utils.LogHandler import logger from conf import settings class AllureOperate(object): def get_allure_report(self): """ 生成报告 """ # os.system(ALLURE_COMMAND) # subprocess.Popen(ALLURE_COMMAND, shell=True) logger().info('正在生成测试报告.....') subprocess.call(settings.ALLURE_COMMAND, shell=True) logger().info('生成测试报告成功.....') ''' ["allure", "generate", ] ''' def check_zip(self): """ 打包 """ try: logger().info('正在打包测试报告.....') # BASE_DIR:当前脚本的父级目录 BASE_DIR = os.path.join(settings.BASE_DIR, 'report') start_zip_dir = os.path.join(BASE_DIR, 'allure_result') # 要压缩文件夹的根路径 zip_file_name = 'allure_report.zip' # 为压缩后的文件起个名字 # 下面是固定写法 zip_file_path = os.path.join(BASE_DIR, zip_file_name) f = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) for dir_path, dir_name, file_names in os.walk(start_zip_dir): # 要是不replace,就从根目录开始复制 file_path = dir_path.replace(start_zip_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() logger().info('打包测试报告完成.....') except Exception as e: logger().error('打包测试报告失败: {}'.format(e)) def send_mail(self): # 第三方 SMTP 服务 mail_host = settings.MAIL_HOST # 设置服务器 # 勿动 mail_user = settings.MAIL_USER # 用户名 mail_pass = settings.MAIL_TOKEN # 口令 # 设置收件人和发件人 sender = settings.SENDER receivers = settings.RECEIVERS # 创建一个带附件的实例对象 message = MIMEMultipart() # 邮件主题、收件人、发件人 subject = settings.THEME # 邮件主题 message['Subject'] = Header(subject, 'utf-8') message['From'] = Header("{}".format(sender), 'utf-8') # 发件人 message['To'] = Header("{}".format(';'.join(receivers)), 'utf-8') # 收件人 # 邮件正文内容 send_content = settings.SEND_CONTENT content_obj = MIMEText(send_content, 'plain', 'utf-8') # 第一个参数为邮件内容 message.attach(content_obj) # 构造附件 att = MIMEText(_text=self._get_zip_file(), _subtype='base64', _charset='utf-8') att["Content-Type"] = 'application/octet-stream' att["Content-Disposition"] = 'attachment; filename="{}"'.format(settings.SEND_FILE_NAME ) # # filename 为邮件附件中显示什么名字 message.attach(att) try: smtp_obj = smtplib.SMTP() smtp_obj.connect(mail_host, 25) # 25 为 SMTP 端口号 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('email send error: {}'.format(e)) def _get_zip_file(self): """ 获取zip文件内容 """ with open(file=os.path.join(settings.BASE_DIR, 'report', 'allure_report.zip'), mode='rb') as f: return f.read() if __name__ == '__main__': AllureOperate().get_allure_report()
allure生成测试报告的优化

import os import shutil import pytest import allure from deepdiff import DeepDiff from utils.ExcelHandler import ExcelOperate from utils.RequestsHandler import RequestsOperate from utils.LogHandler import logger from conf import settings excel_data_list = ExcelOperate(settings.FILE_PATH, 3).get_excel() class TestCase(object): # @classmethod # def setup_class(cls): # cls.excel_data_list = ExcelOperate(settings.FILE_PATH, 0).get_excel() @pytest.mark.parametrize('item', excel_data_list) def test_case(self, item): logger().info('正在进行断言.....') except_date, result = RequestsOperate(current_case=item, all_excel_data_list=excel_data_list).get_response_msg() # print(DeepDiff(except_date, result)) # 生成报告的优化 allure.dynamic.title(item['title']) allure.dynamic.description( "<b style='color:red'>请求的url:</b>{}<hr />" "<b style='color:red'>预期值: </b>{}<hr />" "<b style='color:red'>实际执行结果: </b>{}<hr />".format(item['url'], item['except'], result) ) # 断言处理 # get 如果能取到values_changed就返回,娶不到就返回none assert not DeepDiff(except_date, result).get('values_changed', None) logger().info('完成断言,{}'.format(except_date, result))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?