1.业务层
#封装店铺的类
from configs.config import HOST
import requests
from lib.apiLib.login import Login
class Shop:
def __init__(self,inToken):
self.headers = {'Authorization':inToken}#身份信息校验--鉴权
#1- 列出店铺
def shop_list(self,inData):
url = f'{HOST}/shopping/myShop'#url
#2- body
payload = inData
resp = requests.get(url,params=payload,headers = self.headers)
return resp.json()#请求的响应数据
#2- 编辑接口里的一个图片上传接口
def file_upload(self,fileName,fileDir):
'''
:param fileName: 文件名
:param fileDir: 文件路径
:return: 图片的信息
'''
#请求的要素: url , headers-token ,请求体:文件名,文件对象,文件类型
file_url = f'{HOST}/file'
file_body = {'file':(fileName,open(fileDir,'rb'),'image/png')}#请求体文件(文件名,文件对象,文件类型)
resp = requests.post(file_url,headers=self.headers,files = file_body)
return resp.json()['data']['realFileName']#图片信息
#3- 编辑店铺
def shop_update(self,inData,shopId,imageInfo):
'''
:param inData: 用例数据---用例数据
:param shopId: 商铺id---动态获取---shop_list去获取(前置)
:param imageInfo: 图片信息---动态获取
:return:
'''
inData['id'] = shopId#更新用例传入的数据id
inData['image_path'] = imageInfo # ['data']['realFileName']#图片信息
inData['image'] = f"{HOST}/file/getImgStream?fileName={imageInfo}"
payload = inData
url = f'{HOST}/shopping/updatemyshop'
resp = requests.post(url,data=payload,headers = self.headers)
return resp.json()
2.conftest.py
import pytest
'''
scope:
1- 默认是function 只要是函数级别都会调用下面的代码
2- class 类级别 每一个运行一次下面的代码
3- session 整个包作用域只运行一次
4- moudle 整个py文件(模块)作用域只运行一次
autouse 是否自动调用
'''
import os
@pytest.fixture(scope='session',autouse=True)
def clear_report():#前置操作---这个作用域---操作前的动作
print('---现在我们开始自动化测试--环境初始化操作---')
# 本地运行处理历史数据
try:
for one in os.listdir('../report/tmp'):#os.listdir('../report/tmp')列出路径下所有的文件
if 'json' in one:
os.remove(f'../report/tmp/{one}')
except:#规避第一次运行,没有生成tmp文件夹
print('pytest---是第一次运行')
yield #这个作用域操作后的--动作
print('---自动化测试执行完成,现在进行数据清除操作---')
#2- 店铺更新接口的初始化操作
'''
需求参数:shopID imageInfo
前置动作:
1- 登录
2- 列出店铺
初始化操作是否自动调用:手动调用---哪里需要在哪里调用
'''
from lib.apiLib.login import Login
from lib.apiLib.shop import Shop
@pytest.fixture(scope='function')
def shop_update_init():
#1- 登录
token = Login().login({'username': 'sq0777', 'password': 'xintian'}, getToken=True)
#2- 列出店铺
shop = Shop(token)#创建实例
shopID=shop.shop_list({"page":1,"limit":20})['data']['records'][0]['id']
#3- 获取图片信息
imageInfo = shop.file_upload('123.png','../data/123.png')
# print(shopID)
# print(imageInfo)
return shopID,imageInfo#返回值
#3- 文件上接口调用获取imageInfo
@pytest.fixture(scope='module',autouse=True)
def login_init():#前置操作---这个作用域---操作前的动作
print('---现在我们开始自动化测试--登录操作---')
# 本地运行处理历史数据
token = Login().login({'username': 'sq0777', 'password': 'xintian'}, getToken=True)
return token
@pytest.fixture(scope='function')
def shop_xt():#前置操作---这个作用域---操作前的动作
print('---useFixture---')
3.test_shop.py
from tools.yamlControl import get_yaml_data
from lib.apiLib.login import Login
from lib.apiLib.shop import Shop
import pytest,os
from tools.excelControl import get_excelData
import allure
@pytest.mark.shop#增加标签 mark
@allure.epic('外卖项目-接口测试')
@allure.feature('店铺模块')#测试类
class TestShop:
def setup_class(self):#使用这个类就一定会调用这个方法,只能运行一次
#1- 登录
res = get_yaml_data('../configs/conf.yaml')
print(res)
self.token = Login().login({'username':'sq0777','password':'xintian'},getToken=True)
#2- 创建一个店铺的实例
self.shop = Shop(self.token)
#1- 列出店铺
#没有返回返回值的fixture
# @pytest.mark.usefixtures('shop_xt')
# #@pytest.mark.skip(reason='下面接口后端存在问题,先跳过不执行!')#无条件跳过下面的用例执行--相当于注释--报告会显示
# @allure.story('店铺列出')#接口的名称
# @allure.link('https://www.baidu.com')
# @allure.title('店铺列出用例') # 用例的标题
# @pytest.mark.shop_list# 增加标签 mark
# @pytest.mark.parametrize('inData,respData',get_excelData('我的商铺','listshopping'))
# def test_shop_list(self,inData,respData):
# #1- 调用店铺列出接口
# #店铺实例的创建必须要登录--需要一个店铺的实例---才能使用对应的方法
# #调用对应的方法
# res = self.shop.shop_list(inData)
# '''
# 如果断言不是一个属性,需要多个组合判断?
# 原理:assert 布尔表达式 多个条件使用and or
# '''
# if 'code' in respData:
# assert res['code'] == respData['code']
# else:
# assert res['error'] == respData['error']
#
#2- 店铺更新
@pytest.mark.run(order=1)#指定运行顺序! 最后@pytest.mark.last
# @pytest.mark.skipif(Login().login({'username':'sq0777','password':'123456'})['msg']!='成功',reason='登录失败,先跳过不执行下面的接口!') # 条件为真,跳过下面的用例执行
@allure.story('店铺更新') # 接口的名称
@allure.title('店铺更新用例')#用例的标题
@pytest.mark.shop_update # 增加标签 mark
@pytest.mark.parametrize('inData,respData', get_excelData('我的商铺', 'updateshopping'))
def test_shop_update(self,inData,respData,shop_update_init):#引入初始化方法名字
#1- 调用店铺更新的接口
#shop_update_init[0],shop_update_init[1] 返回值0 返回值1
#res = self.shop.shop_update(inData,shop_update_init[0],shop_update_init[1])#excel测试用例数据,店铺的id,图片信息
#用例执行步骤
with allure.step('第1步:登录'):
print('登录')
with allure.step('第2步:店铺更新操作'):#计算这一步的时间--执行时间
res = self.shop.shop_update(inData,shop_update_init[0],shop_update_init[1])
#2- 断言
assert res['code'] == respData['code']
if __name__ == '__main__':
'''
pytest.main : -m 指定标签运行 ‘-m’,'shop_list'
'''
#1 -m
#pytest.main(['test_shop.py', '-s', '-m','shop_list or shop_update','--alluredir', '../report/tmp']) # -s 打印print信息
#2 -k 匹配用例文件名称** xxx.py
#pytest.main(['test_shop.py', '-s', '-k', 'shop', '--alluredir', '../report/tmp']) # -s 打印print信息
pytest.main(['test_shop.py', '-s', '--alluredir', '../report/tmp'])
os.system('allure serve ../report/tmp')
4.excel操作
import xlrd#要安装的第三库 pip install xlrd
import json
#json字符串---转化成---字典 转化后的值(字典类型) = json.loads(被转json字符串)
#字典---转化成---json字符串 转化后的值(json类型) = json.dumps(被转字典)
def get_excelData(sheetName,caseName):
resList = []#存放结果
#1- 加载 ---打开
excelDir = '../data/外卖系统接口测试用例-V1.5.xls'
#formatting_info=True 保持原表样式
workBook = xlrd.open_workbook(excelDir,formatting_info=True)
#2-打开对应的操作的子表
workSheet = workBook.sheet_by_name(sheetName)
#3- 读数据--读取指定单元格数据
#读取一行--print(workSheet.row_values(1))
#请求体
# print(workSheet.cell(1,9).value)#cell(行号,列号).value
# #响应数据
# print(workSheet.cell(1,11).value)#cell(行号,列号).value
#print(workSheet.col_values(0))#获取用例编号列数据
idx = 0#excel第0行数据
for one in workSheet.col_values(0):#6次
if caseName in one:#运行你需要运行的测试用例
reqBodyData = workSheet.cell(idx,9).value#cell(行号,列号).value
respData = workSheet.cell(idx, 11).value#cell(行号,列号).value
#[(),(),()]
resList.append((json.loads(reqBodyData),json.loads(respData)))
idx += 1#轮询下一行数据
return resList
#新建空的excel xlwt
from xlutils.copy import copy#拷贝函数
def set_excelData(sheetIndex):#1- 新建空excel文件写入 2- 打开原来的写入---能不能复制一个出来去写入
#1- 加载 ---打开
excelDir = '../data/外卖系统接口测试用例-V1.5.xls'
#formatting_info=True 保持原表样式
workBook = xlrd.open_workbook(excelDir,formatting_info=True)
workBookNew = copy(workBook)
workSheetNew = workBookNew.get_sheet(sheetIndex)#打开复制的excel的其中一个表--登录模块
return workBookNew,workSheetNew
import pprint
if __name__ == '__main__':
get_excelData('登录模块','Login')
5.yaml文件操作
#读取yaml文件数据
import yaml
def get_yaml_data(fileDir):
#1- 文件在磁盘--需要在缓存里打开
fo = open(fileDir,'r',encoding='utf-8')
#2- 调用方法
res = yaml.load(fo,Loader=yaml.FullLoader)
return res
# print(res['info'])
# print(res['msg'])
def get_yamls_data(fileDir):#获取yaml里多个文件
resList = []
#1- 文件在磁盘--需要在缓存里打开
fo = open(fileDir,'r',encoding='utf-8')
#2- 调用方法
res = yaml.load_all(fo,Loader=yaml.FullLoader)
for one in res:
print(one)
resList.append(one)
return resList
def set_yaml_data(fileDir):#写不需要去新建文件
#1- 文件在磁盘--需要在缓存里打开
fo = open(fileDir,'w',encoding='utf-8')
#2- 调用方法
yaml.dump({'a':100,'b':200,'c':[10,20]},fo)
def get_yaml_data02(fileDir):#优化--所有去读取文件--加上异常机制--log日志机制
resList = []
#1- 文件在磁盘--需要在缓存里打开
fo = open(fileDir,'r',encoding='utf-8')
#2- 调用方法
res = yaml.load(fo,Loader=yaml.FullLoader)
for one in res:
resList.append((one['data'],one['resp']))
return resList
if __name__ == '__main__':
# get_yamls_data('../configs/conf.yaml')
# set_yaml_data('../configs/test.yaml')
print(get_yaml_data('../configs/conf.yaml'))
6.mock
import requests
#1- url
# url = 'http://127.0.0.1:9999/xintian_sq'
# resp = requests.get(url)
# print(resp.text)
# url2 = 'http://127.0.0.1:9999/xintian'
# resp = requests.get(url2)
# print(resp.text)
#3- get--带参数的
# url2 = 'http://127.0.0.1:9999/xintian2'
# payload = {
# "key1":"abc",
# "key2":"123"
# }
# resp = requests.get(url2,params=payload)
# print(resp.text)
#4- post--带参数的
# url2 = 'http://127.0.0.1:9999/xt3'
# payload = {"key1":"abc"}
# resp = requests.post(url2,data=payload)
# print(resp.text)
异步接口测试
HOST = 'http://127.0.0.1:9999'
#1- 申请退订请求
def create_order():
url = f'{HOST}/api/order/create/'
payload = {
"user_id": "sq123456",
"goods_id": "20200815",
"num":1,
"amount":200.6
}
resp = requests.post(url,json=payload)
print(resp)
#resp.request.headers#请求头
#resp.headers响应头
return resp.json()
#2- 查询结果接口
'''
一般异步的查询接口:
1、频率
2、超时
'''
import time
def get_order_result(orderID,interval,timeout):
url = f'{HOST}/api/order/get_result/'
payload = {"order_id": orderID}#参数
#1- 开始时间:获取当前的时间
startTime = time.time()
#2-结束时间:开始+超时时间
cnt = 0#为了显示效果--打印下查询的次数
endTime = startTime+timeout
while time.time()<endTime:#for:适用于:遍历操作、知道需要循环多少次的 while :靠条件结束
resp = requests.get(url,params=payload)
print(f'第{cnt}次查询')
cnt +=1
time.sleep(interval) # 查询的频率 单位是s
if resp.text:#一定有响应,我们就退出
break
return resp.text
if __name__ == '__main__':
orderId = create_order()['order_id']#1- 申请退订请求
# print(get_order_result(orderId,2,10))#2- 查询接口
# 扩展--pytest 有没有用例失败了重跑的机制 失败了可以按照一定频率去跑多次 频率+次数
import pytest
#reruns=2 重跑次数 ,reruns_delay=2 频率
@pytest.mark.flaky(reruns=2,reruns_delay=2)#只要失败的才重跑
@pytest.mark.parametrize('a',[100,200,300])
def test_001(a):
assert a == 200
if __name__ == '__main__':
pytest.main(['mockTest.py','-s'])
7.日志模块
#需要导入一个库
import logging#标准库里
'''
日志的要素:
1- 日志名称
2- 日志的级别
3- 日志文件中描述的格式
'''
import datetime#具体时间的
def logger():
#1- 日志名称 路径+名字+后缀名
fileName = f'../logs/{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}.log'
#2- 创建日志对象
log = logging.getLogger()# __name__
#3- 设置日志级别
log.setLevel(logging.INFO)
rHandler = logging.FileHandler(fileName,mode='w',encoding='utf-8')
#4- 设置日志的内容格式 时间 - 级别 -模块名[行号] - 信息 s 字符串 d-int
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s[line:%(lineno)d] - %(message)s')
#5- 设置格式
rHandler.setFormatter(formatter)
log.addHandler(rHandler)
return log
if __name__ == '__main__':
log = logger()
# print(datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
log .info('hello')
log .error('xintian')
8.封装日志
#封装日志
class EncapLogs():
def __init__(self,name=None): #name为日志的名称
if name is None:
self.name = 'testcase'
else:
self.name = name
self.mylog = logging.getLogger(self.name)
#创建日志对象
self.mylog.setLevel(do_Encapyaml.getyaml_info('log','logger_level'))#设置日志等级
#创建显示日志渠道
consle_log = logging.StreamHandler() #输出到控制台
#控制台日志设置日志等级
consle_log.setLevel('WARNING')
#日志文件
log_addr = do_getpath.getpath('logs_path') + do_Encapyaml.getyaml_info('log','log_filename')
#设置日志文件:文件地址、编码格式
log_file = logging.FileHandler(log_addr,encoding ='utf-8')
#输出到日志文件
#设置日志的格式:日志记录的运行时间,日志报错等级,日志提示信息,日志的名称,第几行代码
log_formater = logging.Formatter('%(asctime)s - [%(levelname)s] - [msg]: %(message)s - %(name)s - %(lineno)d')
#把日志的格式加入到日志渠道中
#formatter= '%(asctime)s-[%(levelname)s]-[msg]:%(message)s-%(name)s-%(lineno)d'
# % (levelno)s:打印日志级别的数值。
# % (levelname)s:打印日志级别的名称。
# % (pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。
# % (filename)s:打印当前执行程序名。
# % (funcName)s:打印日志的当前函数。
# % (lineno)d:打印日志的当前行号。
# % (asctime)s:打印日志的时间。
# % (thread)d:打印线程ID。
# % (threadName)s:打印线程名称。
# % (process)d:打印进程ID。
# % (processName)s:打印线程名称。
# % (module)s:打印模块名称。
# % (message)s:打印日志错误提示信息
consle_log.setFormatter(log_formater)
log_file.setFormatter(log_formater)
#把日志对象与输出渠道相关联
self.mylog.addHandler(consle_log)
self.mylog.addHandler(log_file)
def runlogs(self) :
return self.mylog
do_log = EncapLogs().runlogs()
if __name__ == '__main__':
# do_log = EncapLogs()
# mylog = do_log.runlogs()
# mylog.info('这只是个提示')
# mylog.error('错误信息')
# mylog.critical('这是一个致命的错误')
# mylog.debug('这是一个调试信息')
# mylog.warning('警告')
do_log.error('错误信息提示')
日志使用
@ddt
class UnittestAdd(unittest.TestCase):
filename = do_Encapyaml.getyaml_info('excel', 'filename')
sheetname = do_Encapyaml.getyaml_info('excel', 'sheetname4')
fileaddr = os.path.join(do_getpath.getpath('data_path'), filename)
# 获取excel文件的地址
my_date = Encapsulates_excel(fileaddr, sheetname)
testcases = my_date.r_excel()
@classmethod
def setUpClass(cls):
cls.do_request = HandleRequests()
cls.do_request.add_headers(do_Encapyaml.getyaml_info("api", "api_version"))
cls.reg = RegStr()
cls.mysql = LinkMysql()
setattr(GobleData,'${not_existed_id}',cls.mysql.not_existed_user_id())
do_log.info('开始执行用例')
@classmethod
def tearDownClass(cls):
cls.do_request.close()
cls.mysql.close()
do_log.info('用例执行结束')
@data(*testcases)
def test_add(self,onetestcase):
new_data = self.reg.RegulaStr(onetestcase.data)
res = self.do_request.send_requests(onetestcase.method,onetestcase.url,json = new_data)
real_code = res.json()['code']
try:
self.assertEqual(onetestcase.expected_value,real_code,onetestcase.name)
except AssertionError as err:
do_log.error(f'{onetestcase.name}具体异常信息为:{err}')
self.my_date.write_date(onetestcase,res.text,'失败')
raise err
else:
if 'token_info' in res.text:
token = res.json()['data']['token_info']['token']
new_headers = {"Authorization":"Bearer " + token}
self.do_request.add_headers(new_headers)
self.my_date.write_date(onetestcase,res.text,'成功')