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

结合单元测试框架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.mzph.cn/news/83490.shtml

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

相关文章

集成Activiti-Modeler流程设计器

集成Activiti-Modeler流程设计器 Activiti Modeler 是 Activiti 官方提供的一款在线流程设计的前端插件,可以方便流程设计与开发人员绘制流程图,保存流程模型,部署至流程定义等等。 1、材料准备 首先我们需要获取activiti-explorer.zip&…

zabbix监控平台部署(二)

目录 一、自定义监控 二、Nginx监控 三、监控mysql 四、钉钉告警 五、163邮箱报警 总结 zabbix5.0 一、自定义监控 zabbix-agent(147) agent端操作 vim /etc/zabbix/zabbix_agentd.conf 在配置未文件末尾添加 UserParametermemory_userd,free…

vue修改node_modules打补丁步骤和注意事项

当我们使用 npm 上的第三方依赖包,如果发现 bug 时,怎么办呢? 想想我们在使用第三方依赖包时如果遇到了bug,通常解决的方式都是绕过这个问题,使用其他方式解决,较为麻烦。或者给作者提个issue,然…

【Java 基础篇】Java后台线程和守护线程详解

在Java多线程编程中,有两种特殊类型的线程:后台线程(Daemon Thread)和守护线程(Daemon Thread)。这两种线程在一些特定的场景下非常有用,但也需要谨慎使用。本文将详细介绍后台线程和守护线程的…

(JavaEE)(多线程案例)线程池 (简单介绍了工厂模式)(含经典面试题ThreadPoolExector构造方法)

线程诞生的意义,是因为进程的创建/销毁,太重了(比较慢),虽然和进程比,线程更快了,但是如果进一步提高线程创建销毁的频率,线程的开销就不能忽视了。 这时候我们就要找一些其他的办法…

Ansible之Playbook的任务控制

一)Ansible 任务控制基本介绍 这⾥主要来介绍PlayBook中的任务控制。 任务控制类似于编程语⾔中的if … 、for … 等逻辑控制语句。 这⾥我们给出⼀个实际场景应⽤案例去说明在PlayBook中,任务控制如何应⽤。 在下⾯的PlayBook中,我们创建了…

pnpm入门教程

一、概述 1、更小 使用 npm 时,依赖每次被不同的项目使用,都会重复安装一次。 而在使用 pnpm 时,依赖会被存储在内容可寻址的存储中。 2、更快 依赖解析。 仓库中没有的依赖都被识别并获取到仓库。目录结构计算。 node_modules 目录结构是…

什么是GPT磁盘?介绍GPT(GUID 分区表)磁盘及其优势!

GPT概述 GPT磁盘是什么意思?GPT是全局唯一标识符分区表(GUID Partition Table)的简称,它是硬盘分区表结构的一个标准模式。在我们深入了解GPT磁盘的特性之前须知,MBR磁盘的分区信息直接保存在主引导记录&#xff0…

【探索C语言中VS调试技巧】:提高效率和准确性

文章目录 前言1. 什么是bug?2. 调试是什么?有多重要?2.1 调试是什么?2.2 调试的基本步骤2.3 Debug和Release的介绍 3. Windows环境调试介绍3.1 调试环境的准备3.2 学会快捷键3.3 调试的时候查看程序当前信息3.3.1 查看临时变量的值…

【C++】动态内存管理 ③ ( C++ 对象的动态创建和释放 | new 运算符 为类对象 分配内存 | delete 运算符 释放对象内存 )

文章目录 一、C 对象的动态创建和释放1、C 语言 对象的动态创建和释放 的方式2、C 语言 对象的动态创建和释放 的方式 二、代码示例 - 对象的动态创建和释放 一、C 对象的动态创建和释放 使用 C 语言中的 malloc 函数 可以为 类对象 分配内存 ; 使用 free 函数可以释放上述分配…

Android 富文本SpannableString

一、认识SpannableString 为什么要使用富文本 在Android开发中,有很多UI会画出一些特别炫酷的界面出来,比如一个字符串里有特殊的字会有其他颜色并加粗、变大变小、插入小图片、给某几个文字添加边框,如果我们使用笨办法用几个TextView或者Im…

单片机第三季-第三课:STM32开发板原理图、配置、浮点运算单元

目录 1,开发板原理图 2,浮点运算单元(FPU) 1,开发板原理图 课程视频比较早,介绍了三款开发板。观看视频时用的开发板说和51单片机共板的STM32核心板,将51单片机从底座拆下来后,安…

avi怎么转换成视频?

avi怎么转换成视频?在我们日常使用的视频格式中,AVI是一种常见且经常被使用的音频视频交叉格式之一。它的优点之一是占用的存储空间相对较小,但也明显存在着画质损失的缺点。虽然AVI格式的视频在某种程度上也很常见,但与最常见的M…

zabbix的原理与安装

一、Zabbix介绍 1、zabbix 是什么? zabbix是一个开源的IT基础监控软件,能实时监控网络服务,服务器和网络设备的状态,如网络使用,CPU负载、磁盘空间等,主要是包括数据的收集、报警和通知的可视化界面zabbi…

VHOST-SCSI代码分析(1)VHOST SCSI设备模拟

VHOST SCSI设备的模拟是由QEMU和HOST共同实现的,QEMU模拟VHOST SCSI设备配置空间等,而对于虚拟机通知HOST和HOST通知虚拟机机制由HOST内核实现。 在QEMU中VHOST SCSI设备继承关系如下: 其它设备以及对应class_init函数和realize具现化实现与V…

LLM微调(一)| 单GPU使用QLoRA微调Llama 2.0实战

最近LLaMA 2在LLaMA1 的基础上做了很多优化,比如上下文从2048扩展到4096,使用了Grouped-Query Attention(GQA)共享多头注意力的key 和value矩阵,具体可以参考: 关于LLaMA 2 的细节,可以参考如下…

zotero通过DOI快速导入文献

之前我经常采用两种方式导入文献: (1)下载PDF,然后拖入zotero 这种方法比较费时间,有些文献无法下载pdf (2)通过google scholar检索文献,然后点击引用——EndNote,chorme…

Kotlin中函数的基本用法以及函数类型

函数的基本用法 1、函数的基本格式 2、函数的缺省值 可以为函数设置指定的初始值&#xff0c;而不必要传入值 private fun fix(name: String,age: Int 2){println(name age) }fun main(args: Array<String>) {fix("张三") }输出结果为&#xff1a;张三2 …

WebGL层次模型——多节点模型

目录 多节点模型 MultiJointModel中的层次结构 控制各部件旋转角度的变量 示例程序——共用顶点数据&#xff0c;通过模型矩阵缩放实现&#xff08;MultiJointModel.js&#xff09; MultiJointModel.js&#xff08;按键响应部分&#xff09; MultiJointModel.js&#x…

刷题日记——将x减到0的最小操作数

将x减到0的最小操作数 题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 题目解读 题目要求移除元素总和等于参数x&#xff0c;这道题给我的第一感觉就是从数组的两边入手&#xff0c;对数据进行加和删除&#xff0c;但是这里有一…