pytest-接口自动化实战

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,'成功')
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
pytest接口自动化实战项目可以通过以下步骤来执行指定的测试用例: 1. 打开命令行终端。 2. 使用pytest命令,并指定要执行的测试文件、测试类和测试方法。例如,可以使用以下命令来执行名为test_demo1.py中TestClass类的test_method方法的测试用例: ``` pytest testcase/test_demo1.py::TestClass::test_method ``` \[2\] 此外,如果你对Python编程还不熟悉,可以先学习Python编程入门到精通,这将为你提供必要的编程基础。\[1\] 如果你对接口自动化测试开发框架有兴趣,可以参考2023最新合集Python自动化测试开发框架的教程,该合集包含了全栈、实战和教程等多个方面的内容,可以帮助你更好地理解和应用接口自动化测试开发框架。\[3\] #### 引用[.reference_title] - *1* *2* [超详细从入门到精通,pytest自动化测试框架实战教程(一)](https://blog.csdn.net/m0_70102063/article/details/129860191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [测试老鸟手把手教你python接口自动化测试项目实战演示](https://blog.csdn.net/MXB_1220/article/details/129865969)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凡晨丹心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值