Pytest接口自动化测试实战演练

news/2024/4/27 12:13:31/文章来源:https://blog.csdn.net/m0_68405758/article/details/130371883

结合单元测试框架pytest+数据驱动模型+allure

目录

api: 存储测试接口conftest.py :设置前置操作目前前置操作:1、获取token并传入headers,2、获取命令行参数给到环境变量,指定运行环境commmon:存储封装的公共方法connect_mysql.py:连接数据库http_requests.py: 封装自己的请求方法logger.py: 封装输出日志文件read_yaml.py:读取yaml文件测试用例数据read_save_data.py:读取保存的数据文件case: 存放所有的测试用例data:存放测试需要的数据save_data: 存放接口返回数据、接口下载文件test_data: 存放测试用例依赖数据upload_data: 存放上传接口文件logs: 存放输出的日志文件report: 存放测试输出报告getpathinfo.py :封装项目测试路径pytest.int :配置文件requirement.txt: 本地python包(pip install -r requirements.txt 安装项目中所有python包)run_main.py: 项目运行文件

结构设计

1.每一个接口用例组合在一个测试类里面生成一个py文件2.将每个用例调用的接口封装在一个测试类里面生成一个py文件3.将测试数据存放在yml文件中通过parametrize进行参数化,实现数据驱动4.通过allure生成测试报告

代码展示

api/api_service.py #需要测试的一类接口

'''
Code description:服务相关接口
Create time: 2020/12/3
Developer: 叶修
'''
import os
from common.http_requests import HttpRequestsclass Api_Auth_Service(object):def __init__(self):self.headers = HttpRequests().headersdef api_home_service_list(self):# 首页服务列表# url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"url = os.environ["host"] + "/v1/auth/auth_service/findAuthService"  # 读取conftest.py文件地址进行拼接response = HttpRequests().get(url, headers=self.headers, verify=False)# print(response.json())return responsedef get_service_id(self):#获取银行卡三要素认证服务idurl = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"#url = os.environ["host"] + "/v1/auth/auth_service/findAuthService"  # 读取conftest.py文件地址进行拼接response = HttpRequests().get(url,headers=self.headers)#print(response.json()['data'][0]['service_list'][0]['id'])service_id = response.json()['data'][0]['service_list'][1]['id']return service_iddef api_service_info(self,serviceId='0b6cf45bec757afa7ee7209d30012ce1',developerId=''):#服务详情body = {"serviceId" :serviceId,"developerId":developerId}url = "http://192.168.2.199:9092/v1/auth/auth_service/findServiceDetail"#url = os.environ["host"] + "/v1/auth/auth_service/findServiceDetail"#读取conftest.py文件地址进行拼接response = HttpRequests().get(url,headers=self.headers,params = body,verify=False)#print(response.json())return responsedef api_add_or_update_service(self,api_param_req,api_param_res,description,error_code,icon,id,interface_remarks,name,product_info,request_method,sample_code,sort,type,url):#服务添加或者更新body={"api_param_req": api_param_req,"api_param_res": api_param_res,"description": description,"error_code": error_code,"icon": icon,"id": id,"interface_remarks": interface_remarks,"name": name,"product_info": product_info,"request_method": request_method,"sample_code": sample_code,"sort": sort,"type": type,"url": url,}#url = "http://192.168.2.199:9092/v1/auth/auth_service/insertOrUpdateService"url = os.environ["host"] + "/v1/auth/auth_service/insertOrUpdateService"  # 读取conftest.py文件地址进行拼接response = HttpRequests().post(url,json=body,headers=self.headers,verify=False)return responsedef api_add_service_price(self,id,max_number,money,service_id,small_number):#服务价格添加body = {"id": id,"max_number": max_number,"money": money,"service_id": service_id,"small_number": small_number}# url = "http://192.168.2.199:9092/v1/auth/auth_service/insertServicePrice"url = os.environ["host"] + "/v1/auth/auth_service/insertServicePrice"  # 读取conftest.py文件地址进行拼接response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)return responsedef api_apply_service(self,developer_id,service_id):#申请服务body ={"developer_id": developer_id,"service_id": service_id}# url = "http://192.168.2.199:9092/v1/auth/auth_service/applyService"url = os.environ["host"] + "/v1/auth/auth_service/applyService"  # 读取conftest.py文件地址进行拼接response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)return responseif __name__ == '__main__':#Auth_Service().api_home_service_list()Api_Auth_Service().get_service_id()#Auth_Service().api_service_info()

 api/get_token.py#获取登录token

'''
Code description:获取token
Create time:2020-12-03
Developer:叶修
'''
import osimport urllib3
from common.http_requests import HttpRequestsclass Get_Token(object):def get_token(self,account='****',password='****'):#url = "http://192.168.2.199:9092/v1/auth/developer/accountLogin"url = os.environ["host"]+"/v1/auth/developer/accountLogin"body = {"account": account,"password": password,}urllib3.disable_warnings()r = HttpRequests().post(url, json=body,verify=False)#print(r.json())token = r.json()['data']['token']params = {"access_token": token}HttpRequests().params.update(params)#更新token到sessionreturn tokenif __name__ == '__main__':print(Get_Token().get_token())

case/test_service_info.py #上面接口某一测试用例

'''
Code description: 服务详情
Create time: 2020/12/3
Developer: 叶修
'''
import sys
import allure
import pytest
from common.logger import Log
from common.read_yaml import ReadYaml
from api.api_auth_service.api_auth_service import Api_Auth_Servicetestdata = ReadYaml("auth_service.yml").get_yaml_data()  # 读取数据@allure.feature('服务详情')
class Test_Service_Info(object):log = Log()@pytest.mark.process@pytest.mark.parametrize('serviceId,developerId,expect', testdata['service_info'],ids=['服务详情'])def test_service_info(self,serviceId,developerId,expect):self.log.info('%s{%s}' % ((sys._getframe().f_code.co_name,'------服务详情接口-----')))with allure.step('获取服务id'):serviceId = Api_Auth_Service().get_service_id()with allure.step('服务详情'):msg = Api_Auth_Service().api_service_info(serviceId,developerId)self.log.info('%s:%s' % ((sys._getframe().f_code.co_name, '获取请求结果:%s' % msg.json())))# 断言assert msg.json()["result_message"] == expect['result_message']assert msg.json()['result_code'] == expect['result_code']assert 'url' in msg.json()['data']

 conftest.py

'''
Code description:配置信息
Create time: 2020/12/3
Developer: 叶修
'''
import os
import pytest
from api.get_token import Get_Token
from common.http_requests import HttpRequests@pytest.fixture(scope="session")
def get_token():'''前置操作获取token并传入headers'''Get_Token().get_token()if not HttpRequests().params.get("access_token", ""):#没有get到token,跳出用例pytest.skip("未获取token跳过用例")yield HttpRequests().reqHttpRequests().req.close()def pytest_addoption(parser):parser.addoption("--cmdhost", action="store", default="http://192.168.1.54:32099",help="my option: type1 or type2")
@pytest.fixture(scope="session",autouse=True)
def host(request):'''获取命令行参数'''#获取命令行参数给到环境变量#pytest --cmdhost 运行指定环境os.environ["host"] = request.config.getoption("--cmdhost")print("当前用例运行测试环境:%s" % os.environ["host"])

 common/connect_mysql.py

'''
Code description: 配置连接数据库
Create time: 2020/12/3
Developer: 叶修
'''
import pymysqldbinfo = {"host":"******","user":"root","password":"******","port":31855
}
class DbConnect():def __init__(self,db_conf,database=""):self.db_conf = db_conf#打开数据库self.db = pymysql.connect(database = database,cursorclass = pymysql.cursors.DictCursor,**db_conf)#使用cursor()方式获取操作游标self.cursor = self.db.cursor()def select(self,sql):#sql查询self.cursor.execute(sql)#执行sqlresults = self.cursor.fetchall()return resultsdef execute(self,sql):#sql 删除 提示 修改try:self.cursor.execute(sql)#执行sqlself.db.commit()#提交修改except:#发生错误时回滚self.db.rollback()def close(self):self.db.close()#关闭连接def select_sql(select_sql):'''查询数据库'''db = DbConnect(dbinfo,database='auth_platform')result = db.select(select_sql)db.close()return resultdef execute_sql(sql):'''执行SQL'''db = DbConnect(dbinfo,database='auth_platform')db.execute(sql)db.close()if __name__ == '__main__':sql = "SELECT * FROM auth_platform.auth_service where name='四要素认证'"sel = select_sql(sql)[0]['name']print(sel)

 common/http_requests.py

'''
Code description: 封装自己的请求类型
Create time: 2020/12/3
Developer: 叶修
'''import requests# 定义一个HttpRequests的类
class HttpRequests(object):req = requests.session()#定义session会话# 定义公共请求头headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36','cookie':''}params = {'access_token':''}# 封装自己的get请求,获取资源def get(self, url='', params='', data='', headers=None, cookies=None,stream=None,verify=None):response = self.req.get(url,params=params,data=data,headers=headers,cookies=cookies,stream=stream,verify=verify)return response# 封装自己的post方法,创建资源def post(self, url='', params='',data='', json='', headers=None, cookies=None,stream=None,verify=None):response = self.req.post(url,params=params,data=data,json=json,headers=headers,cookies=cookies,stream=stream,verify=verify)return response# 封装自己的put方法,更新资源def put(self, url='', params='', data='', headers=None, cookies=None,verify=None):response = self.req.put(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)return response# 封装自己的delete方法,删除资源def delete(self, url='', params='', data='', headers=None, cookies=None,verify=None):response = self.req.delete(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)return response

 common/logger.py

'''
Code description: 封装输出日志文件
Create time: 2020/12/3
Developer: 叶修
'''
import logging,time
import os
import getpathinfopath = getpathinfo.get_path()#获取本地路径
log_path = os.path.join(path,'logs')# log_path是存放日志的路径
# 如果不存在这个logs文件夹,就自动创建一个
if not os.path.exists(log_path):os.mkdir(log_path)class Log():def __init__(self):#文件的命名self.logname = os.path.join(log_path,'%s.log'%time.strftime('%Y_%m_%d'))self.logger = logging.getLogger()self.logger.setLevel(logging.DEBUG)#日志输出格式self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')def __console(self,level,message):#创建一个fileHander,用于写入本地fh = logging.FileHandler(self.logname,'a',encoding='utf-8')fh.setLevel(logging.DEBUG)fh.setFormatter(self.formatter)self.logger.addHandler(fh)#创建一个StreamHandler,用于输入到控制台ch = logging.StreamHandler()ch.setLevel(logging.DEBUG)ch.setFormatter(self.formatter)self.logger.addHandler(ch)if level == 'info':self.logger.info(message)elif level == 'debug':self.logger.debug(message)elif level == 'warning':self.logger.warning(message)elif level == 'error':self.logger.error(message)#避免日志重复self.logger.removeHandler(fh)self.logger.removeHandler(ch)#关闭打开文件fh.close()def debug(self,message):self.__console('debug',message)def info(self,message):self.__console('info',message)def warning(self,message):self.__console('warning',message)def error(self,message):self.__console('error',message)if __name__ == '__main__':log = Log()log.info('测试')log.debug('测试')log.warning('测试')log.error('测试')

 common/read_save_data.py

'''
Code description: 读取保存数据
Create time: 2020/12/8
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class Read_Save_Date():def __init__(self):path = getpathinfo.get_path()#获取本地路径self.head_img_path = os.path.join(path,'data','save_data')+"/"+'head_img_path.txt'# head_img_path文件地址self.order_id_path = os.path.join(path, 'data', 'save_data') + "/" + 'order_id.txt'  # order_id.txt文件地址def get_head_img_path(self):# 获取head_img_pathwith open(self.head_img_path, "r", encoding="utf-8")as f:return f.read()def get_order_id(self):# 获取order_idwith open(self.order_id_path, "r", encoding="utf-8")as f:return f.read()if __name__ == '__main__':print(Read_Save_Date().get_head_img_path())print(Read_Save_Date().get_order_id())

 common/read_yaml.py

'''
Code description: 读取yml文件测试数据
Create time: 2020/12/3
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class ReadYaml():def __init__(self,filename):path = getpathinfo.get_path()#获取本地路径self.filepath = os.path.join(path,'data','test_data')+"/"+filename#拼接定位到data文件夹def get_yaml_data(self):with open(self.filepath, "r", encoding="utf-8")as f:# 调用load方法加载文件流return yaml.load(f,Loader=yaml.FullLoader)if __name__ == '__main__':data = ReadYaml("auth_service.yml").get_yaml_data()['add_or_update_service']print(data)

data/

 data/test_data/auth_service.yml

home_service_list:- [{'result_code': '0', 'result_message': '处理成功'}]
service_info:- ['','',{'result_code': '0', 'result_message': '处理成功'}]
add_or_update_service:- [['1','1','1','1','1','123456','1','测试','1','1','1','1','1','1'],{'result_code': '0', 'result_message': '处理成功'}]
add_service_price:- ['123456789','10','0','','0',{'result_code': '0', 'result_message': '处理成功'}]
apply_service:- ['','',{'result_code': '0', 'result_message': '处理成功'}]

 logs/

 report/

 getpathinfo.py

'''
Code description:配置文件路径
Create time: 2020/12/3
Developer: 叶修
'''
import osdef get_path():# 获取当前路径curpath = os.path.dirname(os.path.realpath(__file__))return curpathif __name__ == '__main__':# 执行该文件,测试下是否OKprint('测试路径是否OK,路径为:', get_path())

pytest.ini

#pytest.ini
[pytest]
markers = process
addopts = -p no:warnings
#addopts = -v --reruns 1 --html=./report/report.html --self-contained-html
#addopts = -v --reruns 1 --alluredir ./report/allure_raw
#addopts = -v -s -p no:warnings --reruns  1 --pytest_report ./report/Pytest_Report.html

requirements.txt

allure-pytest==2.8.18
allure-python-commons==2.8.18
BeautifulReport==0.1.3
beautifulsoup4==4.9.3
ddt==1.4.1
Faker==4.18.0
Flask==1.1.1
httpie==1.0.3
httplib2==0.9.2
HttpRunner==1.5.8
py==1.9.0
PyMySQL==0.10.1
pytest==6.1.1
pytest-base-url==1.4.2
pytest-cov==2.10.1
pytest-forked==1.3.0
pytest-html==2.1.1
pytest-instafail==0.4.2
pytest-metadata==1.10.0
pytest-mock==3.3.1
pywin32==228
PyYAML==5.3.1
requests==2.22.0
requests-oauthlib==1.3.0
requests-toolbelt==0.9.1

run_main.py

'''
Code description: 运行主流程测试用例
Create time: 2020/11/5
Developer: 叶修
'''
import os
import pytest
if __name__ == '__main__':pytest.main(['-m','process', '-s','--alluredir', 'report/tmp'])#-m运行mark标记文件os.system('allure generate report/tmp -o report/html --clean') # /report/tmp 为存放报告的源文件目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_103526.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决方案:Zotero实现参考文献中英文混排,将英文文献中的“等”转成“et al.”

Zotero 是一款非常实用且易于使用的参考文献管理工具,可帮助用户收集、整理和引用各种类型的文献,包括图书、期刊文章、网页等。在学术写作中起着重要作用。 但是其在中文世界中,运行起来偶尔会出现问题,这里记录一个问题及其解决…

隋唐洛阳“西宫”:上阳宫的GIS视角

隋唐洛阳城简介 营建 隋大业元年(605年),在隋炀帝的授意下,隋代著名城市设计师宇文恺,在汉魏故城以西重新选址,历时8个月,日役劳工200万,兴建新都洛阳城。 城和苑 隋唐洛阳城采用…

eBPF技术介绍

前言 eBPF起源于linux内核,它可以以砂箱程序运行在操作系统内核的特权上下文,高效,安全,易于扩展而不需要修改内核源码或者加载内核模块。 操作系统一直是实现观测,安全和网络功能的最理想的地方,因为内核的…

优思学院|精益管理的理念是什么?

作为一个企业,我们都希望拥有高效率和优异的竞争力。但是,如何才能在竞争激烈的市场中脱颖而出?这时,精益管理理念的出现可以帮助我们。 精益管理的基本概念是什么? 精益管理的核心理念是通过消除浪费来实现生产效率…

Java线程间通信方式(3)

前文了解了线程通信方式中的CountDownLatch, Condition,ReentrantLock以及CyclicBarrier,接下来我们继续了解其他的线程间通信方式。 Phaser Phaser是JDK1.7中引入的一种功能上和CycliBarrier和CountDownLatch相似的同步工具,相…

辛弃疾最经典的10首词

他,文能挥笔填词,武能上马杀敌; 他,被称为“词中之龙”, 他,一生赤子,追求收复山河; 他,是与苏轼齐名的豪放派词人; 他是辛弃疾。 辛弃疾一生怀着赤子之…

IO多路复用——select函数

1.select函数原型和fd_set结构体说明 1.1 select函数原型 ​ 使用 select 这种 IO 多路转接方式需要调用一个同名函数 select,这个函数是跨平台的,Linux、Mac、Windows 都是支持的。程序员通过调用这个函数可以委托内核帮助我们检测若干个文件描述符的…

【MCS-51】51单片机结构原理

至今为止,MCS-51系列单片机有许多种型号的产品:其中又分为普通型51(8031、8051、89S51)和增强型52(8032、8052、89S52等)。它们最大的区别在于存储器配置各有差异。下面我举例子的都是8051这一系列的单片机…

STM32-HAL-定时器(无源蜂鸣器的驱动)

文章目录 一、蜂鸣器的介绍二、常用的无源蜂鸣器的电路三、测试准备四、初始化片上外设4.1 初始化定时器4的通道2为PWM输出模式4.2 编写驱动代码4.3 Logic分析仪查看波形4.4 代码分析 一、蜂鸣器的介绍 有源蜂鸣器: 有源蜂鸣器内部有一个发声电路,也就是“源”&…

数据湖Iceberg-Hive集成Iceberg(3)

文章目录 Hive集成Iceberg环境准备Hive与Iceberg的版本对应关系如下上传jar包,拷贝到Hive的auxlib目录中修改hive-site.xml,添加配置项启动 HMS 服务启动 Hadoop 创建和管理 Catalog默认使用 HiveCatalog指定 Catalog 类型使用 HiveCatalog使用 HadoopCa…

C++学习记录——이십 map和set

文章目录 1、setmultiset 2、map3、map::operator[] 1、set vector/list/deque等是序列式容器,map,set是关联式容器。序列式容器的特点就是数据线性存放,而关联式容器的数据并不是线性,数据之间有很强的关系。 它们的底层是平衡…

在当前互联网行情下,Android想转音视频开发,会有前景吗?

前言 近年来,由于三年疫情的影响,很多公司都开始陆陆续续的在裁员,Android开发工作岗位也是,可能有些从事Android开发的朋友还没有意识到,Android开发岗位正在变少,求职者,僧多粥少&#xff0c…

视频大文件传输的演变:从“卷轴男孩”到自动化

200年前,从纽约市到英国伦敦的单程旅行需要乘坐一艘跨大西洋轮船将近三周——如果你能负担得起的话,那就是。那些不能在满是汗水、狭窄的帆船上安顿大约一个半月的人。 今天,视频专业人士能够在几小时甚至几分钟内跨越相同的物理距离传输大量…

《用于估计血压变化的光电体积描记图和心电图的特征》阅读笔记

目录 一、摘要 二、十大问题 Q1论文试图解决什么问题? Q2这是否是一个新的问题? Q3这篇文章要验证一个什么科学假设? Q4有哪些相关研究?如何归类?谁是这一课题在领域内值得关注的研究员? Q5论文中提…

微信小程序第五节——登录那些事儿(超详细的前后端完整流程)

📌 微信小程序第一节 ——自定义顶部、底部导航栏以及获取胶囊体位置信息。 📌 微信小程序第二节 —— 自定义组件 📌 微信小程序第三节 —— 页面跳转的那些事儿 📌 微信小程序第四节—— 网络请求那些事儿 😜作 …

MFC之CRect详解

2023年4月25日,周二晚上。 今天查了不少关于CRect类及其相关内容的资料,学到了不少东西,所以我决定写一篇详细的关于CRect类及其相关内容的文章,以记录今天所学。 CRect类 在 MFC 中,CRect 类表示一个矩形区域。它是…

linux 命令之 tar -czvf和 tar -xzvf

文章目录 一、概述:二、基础知识 一、概述: tar 用于linux 系统中压缩和解压 二、基础知识 tar常用命令参数说明 tar命令的czvf/xzvf参数分别代表的意义如下: -c 或–create 建立新的备份文件。 -x或–extract或–get 从备份文件中还原文件…

SparkStreaming学习之——无状态与有状态转化、遍历kafka的topic消息、WindowOperations

目录 一、状态转化 二、kafka topic A→SparkStreaming→kafka topic B (一)rdd.foreach与rdd.foreachPartition (二)案例实操1 1.需求: 2.代码实现: 3.运行结果 (三)案例实操2 1.需求: 2.代码实现: 3.运行结果 三、W…

Eclipse代码提示突然失灵的解决方案

不知道改动了啥,突然间Eclipse的代码提示就失效了,发现缺少后极不方便。 使用快捷键:Alt/ 提示 No Default Proposals 为什么使用快捷键:Alt/ 会提示“No Default Proposals。”呢? 网上提示可能是热键冲突 但是一套…

数据可视化大屏电商数据展示平台开发实录(Echarts柱图曲线图、mysql筛选统计语句、时间计算、大数据量统计)

数据可视化大屏电商数据展示平台 一、前言二、项目介绍三、项目展示四、项目经验分享4.1 翻牌器4.1.1 翻牌器-今日实时交易4.1.2.翻牌器后端统计SUM函数的使用 4.2 不同时间指标的数据MySql内部的时间计算 4.3 实时交易播报MySql联表查询和内部遍历循环 4.4 每日交易量4.4.1.近…