1.实现:get/post请求(上传文件)::理论上其他delete/put等请求也实现了,支持restful接口规范
2.发送邮件
3.生成allure测试报告
4.压缩测试报告文件
5.数据依赖
1.通过读取配置文件,获取到host地址、提取token的jsonpath表达式,提取实际响应结果用来与预期结果比对的jsonpath表达式。
2.读取excel用例文件数据,组成一个符合pytest参数化的用例数据,根据每列进行数据处理(token操作、数据依赖)
3.token,写,需要使用一个正常登录的接口,并且接口中要返回token数据,才可以提取,token,读为该请求将携带有token的header,token 无数据的将不携带token
4.数据依赖处理,从excel中读取出来的格式{“用例编号”:[“jsonpath表达式1”, “jsonpath表达式2”]},通过用例编号来获取对应case的实际响应结果(实际响应结果在发送请求后,回写到excel中),通过jsonpath表达式提取对应的依赖参数字段,以及对应的值,最终会返回一个存储该接口需要依赖数据的字典如{“userid”:500, “username”: “zy7y”},在发送请求时与请求数据进行合并,组成一个新的data放到请求中
5.每次请求完成之后将回写实际的响应结果到excel中
6.根据配置文件中配置的jsonpath表达式提取实际响应内容与excel中预期结果的数据对比
7.生成测试报告
8.压缩测试报告文件夹
9.发送邮件
执行接口消耗时间变长,代码乱(语言学的不扎实),频繁读写excel(可考虑用字典存每个接口的实际响应,取值直接从响应字典中取出)整体代码结构优化未实现,导致最终测试时间变长,其他工具单接口测试只需要39ms,该框架中使用了101ms,考虑和频繁读写用例数据导致
名称版本作用python3.7.8pytest6.0.1底层单元测试框架,用来实现参数化,自动执行用例allure-pytest2.8.17allure与pytest的插件可以生成allure的测试报告jsonpath0.82用来进行响应断言操作loguru0.54记录日志PyYAML5.3.1读取yml/yaml格式的配置文件Allure2.13.5要生成allure测试报告必须要在本机安装allure并配置环境变量xlrd1.2.0用来读取excel中用例数据yagmail0.11.224测试完成后发送邮件requests2.24.0发送请求
运行test_api.py -> 读取config.yaml(tools.read_config.py) -> 读取excel用例文件(tools.read_data.py) -> test_api.py实现参数化 -> 处理是否依赖数据 ->base_requests.py发送请求 -> test_api.py断言 -> read_data.py回写实际响应到用例文件中(方便根据依赖提取对应的数据)
server:test: http://127.0.0.1:8888/api/private/v1/# 实例代码使用的接口服务,已改为作者是自己的云服务器部署。(后端源码来自b站:https://www.bilibili.com/video/BV1EE411B7SU?p=10)dev: http://49.232.203.244:8888/api/private/v1/
# 实际响应jsonpath提取规则设置
response_reg:# 提取token的jsonpath表达式token: $.data.token# 提取实际响应的断言数据jsonpath表达式,与excel中预期结果的数据进行比对用response: $.meta
file_path:case_data: ../data/case_data.xlsxreport_data: ../report/data/report_generate: ../report/html/report_zip: ../report/html/apiAutoTestReport.ziplog_path: ../log/运行日志{time}.log
email:# 发件人邮箱user:123456.com# 发件人邮箱授权码password:ASGCSFSGS# 邮箱hosthost:smtp.163.comcontents:解压apiAutoReport.zip(接口测试报告)后,请使用已安装Live Server 插件的VsCode,打开解压目录下的index.html查看报告# 收件人邮箱addressees:["收件人邮箱1","收件人邮箱2","收件人邮箱3"]title:接口自动化测试报告(见附件)# 附件地址enclosures: ["../report/html/apiAutoTestReport.zip",]
请求方法封装
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: base_requests.py
@ide: PyCharm
@time: 2020/7/31
"""
from test import logger
import requests
class BaseRequest(object):def __init__(self):pass# 请求def base_requests(self, method, url, parametric_key=None, data=None, file_var=None, file_path=None, header=None):""":param method: 请求方法:param url: 请求url:param parametric_key: 入参关键字, get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data:param data: 参数数据,默认等于None:param file_var: 接口中接受文件的参数关键字:param file_path: 文件对象的地址, 单个文件直接放地址:/Users/zy7y/Desktop/vue.js多个文件格式:["/Users/zy7y/Desktop/vue.js","/Users/zy7y/Desktop/jenkins.war"]:param header: 请求头:return: 返回json格式的响应"""session = requests.Session()if (file_var in [None, '']) and (file_path in [None, '']):files = Noneelse:# 文件不为空的操作if file_path.startswith('[') and file_path.endswith(']'):file_path_list = eval(file_path)files = []# 多文件上传for file_path in file_path_list:files.append((file_var, (open(file_path, 'rb'))))else:# 单文件上传files = {file_var: open(file_path, 'rb')}if parametric_key == 'params':res = session.request(method=method, url=url, params=data, headers=header)elif parametric_key == 'data':res = session.request(method=method, url=url, data=data, files=files, headers=header)elif parametric_key == 'json':res = session.request(method=method, url=url, json=data, files=files, headers=header)else:raise ValueError('可选关键字为:get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data')logger.info(f'请求方法:{method},请求路径:{url}, 请求参数:{data}, 请求文件:{files}, 请求头:{header})')return res.json()
读取excel用例数据
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: read_data.py
@ide: PyCharm
@time: 2020/7/31
"""
import xlrd
from test import logger
class ReadData(object):def __init__(self, excel_path):self.excel_file = excel_pathself.book = xlrd.open_workbook(self.excel_file)def get_data(self):""":return: data_list - pytest参数化可用的数据"""data_list = []table = self.book.sheet_by_index(0)for norw in range(1, table.nrows):# 每行第4列 是否运行if table.cell_value(norw, 3) == '否':continuevalue = table.row_values(norw)value.pop(3)# 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value)value = tuple(value)logger.info(f'{value}')data_list.append(value)return data_list
存储接口实际结果响应
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest的副本
@author: zy7y
@file: save_response.py
@ide: PyCharm
@time: 2020/8/8
"""
import json
import jsonpath
from test import logger
class SaveResponse(object):def __init__(self):self.actual_response = {}# 保存实际响应def save_actual_response(self, case_key, case_response):""":param case_key:用例编号:param case_response:对应用例编号的实际响应:return:"""self.actual_response[case_key] = case_responselogger.info(f'当前字典数据{self.actual_response}')# 读取依赖数据def read_depend_data(self, depend):""":param depend: 需要依赖数据字典{"case_001":"['jsonpaht表达式1', 'jsonpaht表达式2']"}:return:"""depend_dict = {}depend = json.loads(depend)for k, v in depend.items():# 取得依赖中对应case编号的值提取表达式try:for value in v:# value : '$.data.id'# 取得对应用例编号的实际响应结果actual = self.actual_response[k]# 返回依赖数据的keyd_k = value.split('.')[-1]# 添加到依赖数据字典并返回depend_dict[d_k] = jsonpath.jsonpath(actual, value)[0]except TypeError as e:logger.error(f'实际响应结果中无法正常使用该表达式提取到任何内容,发现异常{e}')return depend_dict
处理依赖数据逻辑
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: data_tearing.py
@ide: PyCharm
@time: 2020/8/10
"""
import json
from json import JSONDecodeError
import jsonpath
from test import logger
class TreatingData(object):"""处理hader/path路径参数/请求data依赖数据代码"""def __init__(self):self.no_token_header = {}self.token_header = {}def treating_data(self, is_token, parameters, dependent, data, save_response_dict):# 使用那个headerif is_token == '':header = self.no_token_headerelse:header = self.token_headerlogger.info(f'处理依赖前data的数据:{data}')# 处理依赖数据dataif dependent != '':dependent_data = save_response_dict.read_depend_data(dependent)logger.debug(f'依赖数据解析获得的字典{dependent_data}')if data != '':# 合并组成一个新的datadependent_data.update(json.loads(data))data = dependent_datalogger.info(f'data有数据,依赖有数据时 {data}')else:# 赋值给datadata = dependent_datalogger.info(f'data无数据,依赖有数据时 {data}')else:if data == '':data = Nonelogger.info(f'data无数据,依赖无数据时 {data}')else:data = json.loads(data)logger.info(f'data有数据,依赖无数据 {data}')# 处理路径参数Path的依赖# 传进来的参数类似 {"case_002":"$.data.id"}/item/{"case_002":"$.meta.status"},进行列表拆分path_list = parameters.split('/')# 获取列表长度迭代for i in range(len(path_list)):# 按着try:# 尝试序列化成dict: json.loads('2') 可以转换成2path_dict = json.loads(path_list[i])except JSONDecodeError as e:# 序列化失败此path_list[i]的值不变化logger.error(f'无法转换字典,进入下一个检查,本轮值不发生变化:{path_list[i]},{e}')# 跳过进入下次循环continueelse:# 解析该字典,获得用例编号,表达式logger.info(f'{path_dict}')# 处理json.loads('数字')正常序列化导致的AttributeErrortry:for k, v in path_dict.items():try:# 尝试从对应的case实际响应提取某个字段内容path_list[i] = jsonpath.jsonpath(save_response_dict.actual_response[k], v)[0]except TypeError as e:logger.error(f'无法提取,请检查响应字典中是否支持该表达式,{e}')except AttributeError as e:logger.error(f'类型错误:{type(path_list[i])},本此将不转换值 {path_list[i]},{e}')# 字典中存在有不是str的元素:使用map 转换成全字符串的列表path_list = map(str, path_list)# 将字符串列表转换成字符:500/item/200parameters_path_url = "/".join(path_list)logger.info(f'path路径参数解析依赖后的路径为{parameters_path_url}')return data, header, parameters_path_url
启动文件
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: test_api.py
@ide: PyCharm
@time: 2020/7/31
"""
import json
import jsonpath
from test import logger
import pytest
import allure
from api.base_requests import BaseRequest
from tools.data_tearing import TreatingData
from tools.read_config import ReadConfig
from tools.read_data import ReadData
from tools.save_response import SaveResponse
# 读取配置文件 对象
rc = ReadConfig()
base_url = rc.read_serve_config('dev')
token_reg, res_reg = rc.read_response_reg()
case_data_path = rc.read_file_path('case_data')
report_data = rc.read_file_path('report_data')
report_generate = rc.read_file_path('report_generate')
log_path = rc.read_file_path('log_path')
report_zip = rc.read_file_path('report_zip')
email_setting = rc.read_email_setting()
# 实例化存响应的对象
save_response_dict = SaveResponse()
# 读取excel数据对象
data_list = ReadData(case_data_path).get_data()
# 数据处理对象
treat_data = TreatingData()
# 请求对象
br = BaseRequest()
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')
class TestApiAuto(object):# 启动方法def run_test(self):import os, shutilif os.path.exists('../report') and os.path.exists('../log'):shutil.rmtree(path='../report')shutil.rmtree(path='../log')# 日志存取路径logger.add(log_path, encoding='utf-8')pytest.main(args=[f'--alluredir={report_data}'])os.system(f'allure generate {report_data} -o {report_generate} --clean')logger.warning('报告已生成')
@pytest.mark.parametrize('case_number,case_title,path,is_token,method,parametric_key,file_var,' 'file_path, parameters, dependent,data,expect', data_list)def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_var,file_path, parameters, dependent, data, expect):""":param case_number: 用例编号:param case_title: 用例标题:param path: 接口路径:param is_token: token操作:写入token/读取token/不携带token:param method: 请求方式:get/post/put/delete....:param parametric_key: 入参关键字:params/data/json:param file_var: 接口中接受文件对象的参数名称:param file_path: 文件路径,单文件实例:/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py多文件实例['/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py','/Users/zy7y/PycharmProjects/apiAutoTest/test/test_api.py']:param parameters: path参数(携带在url中的参数)依赖处理 users/:id(id携带在url中) 实例:{"case_001": '$.data.id'},解析从用例编号为case_001的实际结果响应中提取data字典里面的id的内容(假设提取出来是500), 最后请求的路径将是host + users/500:param dependent: data数据依赖,该接口需要上一个接口返回的响应中的某个字段及内容:实例{"case_001",["$.data.id","$.data.username"]}解析: 从用例case_001的实际响应结果中提取到data下面的id,与username的值(假设id值为500,username为admin),那么提取的数据依赖内容将是{"id":500, "username":"admin"}纳闷最终请求的data 将是 {"id":500, "username":"admin"} 与本身的data合并后的内容:param data: 请求数据:param expect:预期结果,最后与config/config.yaml下的response_reg->response提取出来的实际响应内容做对比,实现断言:return:"""# 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码# 动态添加标题allure.dynamic.title(case_title)logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')with allure.step("处理相关数据依赖,header"):data, header, parameters_path_url = treat_data.treating_data(is_token, parameters, dependent, data, save_response_dict)with allure.step("发送请求,取得响应结果的json串"):res = br.base_requests(method=method, url=base_url + path + parameters_path_url, parametric_key=parametric_key, file_var=file_var, file_path=file_path, data=data, header=header)with allure.step("将响应结果的内容写入实际响应字典中"):save_response_dict.save_actual_response(case_key=case_number, case_response=res)# 写token的接口必须是要正确无误能返回token的if is_token == '写':with allure.step("从登录后的响应中提取token到header中"):treat_data.token_header['Authorization'] = jsonpath.jsonpath(res, token_reg)[0]with allure.step("根据配置文件的提取响应规则提取实际数据"):really = jsonpath.jsonpath(res, res_reg)[0]with allure.step("处理读取出来的预期结果响应"):expect = json.loads(expect)with allure.step("预期结果与实际响应进行断言操作"):assert really == expectlogger.info(f'完整的json响应: {res}\n需要校验的数据字典: {really} 预期校验的数据字典: {expect} \n测试结果: {really == expect}')logger.debug(f'⬆⬆⬆...用例编号:{case_number},执行完毕,日志查看...⬆⬆⬆\n\n️')
if __name__ == '__main__':TestApiAuto().run_test()# 使用jenkins集成将不会使用到这两个方法(邮件发送/报告压缩zip)# from tools.zip_file import zipDir# from tools.send_email import send_email# zipDir(report_generate, report_zip)# send_email(email_setting)
Original: https://blog.csdn.net/text2204/article/details/126382491
Author: 万天峰
Title: Python接口自动化测试工具(Pytest+Allure+jsonpath+xlrd+excel、支持Restful接口规范)
相关阅读
Title: Windows VSCode配置conda shell执行Python脚本
前言
Visual Studio Code(简称VSCode)是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全(又称IntelliSense)、代码重构、查看定义功能,并且内置了命令行工具和Git版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置,也可以通过内置的扩展程序商店安装扩展以拓展软件功能。

Visual Studio Code默认支持非常多的编程语言,包括JavaScript、TypeScript、CSS和HTML;也可以通过下载扩展支持Python、C/C++、Java和Go在内的其他语言。支持功能包括语法高亮、括号补全、代码折叠和代码片段;对于部分语言,可以使用IntelliSense。
作为一款简洁而又强大的编辑器,通过安装插件后可以武装成IDE。作为编码利器,它的设置有时也需要琢磨一番,好的设置可以加快我们的编码和调试速度。
本文主要介绍了在Windows环境下调整VSCode的Python脚本执行terminal为预加载conda环境的shell的过程。
; 方法
注:确保已经在Windows上安装好
conda
,且创建了名为”dev
“的开发环境
打开VSCode的设置,修改”terminal.integrated.profile”选项
①打开File->Preferences->Settings
②在搜索框输入”terminal.integrated.profiles”,可以看到如下内容:

③找到Windows相关的选项,点击”Edit in settings.json”

④可以看到一些设置项,其中”terminal.integrated.profiles.windows”选项中包含了PowerShell、Command Prompt等terminal。
"terminal.integrated.profiles.windows": {
"PowerShell": {
"source": "PowerShell",
"icon": "terminal-powershell"
},
"Command Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [],
"icon": "terminal-cmd"
},
"Git Bash": {
"source": "Git Bash"
}
}
⑤在”terminal.integrated.profiles.windows”选项中增加一项”Conda”,其中路径配置按安装时的路径(如 D:\\ProgramData\\Anaconda3
)设置,激活的环境名(如 dev
)按配置的来:
"terminal.integrated.profiles.windows": {
"PowerShell": {
"source": "PowerShell",
"icon": "terminal-powershell"
},
"Conda": {
"source": "PowerShell",
"args": "-ExecutionPolicy ByPass -NoExit -Command \"& 'D:\\ProgramData\\Anaconda3\\shell\\condabin\\conda-hook.ps1' ; conda activate dev \"",
"icon": "D:\\ProgramData\\Anaconda3\\Lib\\site-packages\\conda\\shell\\conda_icon.ico"
},
"Command Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [],
"icon": "terminal-cmd"
},
"Git Bash": {
"source": "Git Bash"
}
}
⑥保存设置的内容,修改VSCode默认启动terminal:
按”Ctrl+Shift+P”,在弹出的搜索框中输入”Terminal: Select Default Profile”

从新的弹窗中选择设置好的”Conda”:

⑦配置完成,编写Python脚本执行运行过程,则能正常调用Conda环境terminal执行。
版权说明
本文为原创文章,独家发布在blog.csdn.net/TracelessLe。未经个人允许不得转载。如需帮助请email至tracelessle@163.com。

; 参考资料
[1] Visual Studio Code – 维基百科,自由的百科全书
[2] python – Working with Anaconda in Visual Studio Code – Stack Overflow

Original: https://blog.csdn.net/TracelessLe/article/details/117747059
Author: TracelessLe
Title: Windows VSCode配置conda shell执行Python脚本
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/337474/
转载文章受原作者版权保护。转载请注明原作者出处!