python+requests+pytest 接口自动化实现

news2024/9/22 7:33:03

最近工作之余拿公司的项目写了一个接口测试框架,功能还不是很完善,算是抛砖引玉了,欢迎各位来吐槽。
主要思路:
①对 requests 进行二次封装,做到定制化效果
②使用 excel 存放接口请求数据,作为数据驱动
③里面有一些功能模仿了 jmeter,比如用户参数定义、jsonpath 提取
④用 pytest 进行测试用例管理

一、环境

python==3.8.0
requests==2.31.0
pytest==7.4
还有一些其他第三方库例如 allure 报告、jsonpath 等
这块不过多介绍,安装 python 配置环境变量,pip install 命令安装所需插件即可

二、项目目录结构

1、config:用来存

放项目配置文件
包括接口基础配置,数据库信息等
使用的是 configparser 进行读取

2、Outputs 层存放了日志和测试报告,这里报告用的是 allure

3、resources 存放的是测试数据 (这里用的 excel 作为接口数据驱动) 以及其他的一些测试文件等

4、Testcases 存放测试类

5、utils:工具类,包含了读取配置、记录日志、http 请求、接口数据处理等功能

三、重点功能介绍

1、读取 excel

实现代码如下:

from openpyxl import load_workbook

class DoExcel:

    def __init__(self, file_name):  
        # 打开文件
        self.filepath = os.path.join(resources,'case_datas', f'{file_name}.xlsx')
        # 获取工作表
        self.wb = load_workbook(self.filepath)
        # 获取当前sheet
        self.ws = self.wb.active

    def get_data_from_excel(self):
        '''从excel获取测试数据'''
        datas = []
        for row in range(1, self.ws.max_row + 1):
            row_data = {}
            if row > 1:
                # 将行数据转为字典数据格式
                for column in range(1, self.ws.max_column + 1):
                    row_data[f"{self.ws.cell(1, column).value}"] = self.ws.cell(row, column).value

                # 将json_path转字典
                if row_data["json_path"]:
                    row_data["json_path"] = {k: v for item in row_data["json_path"].split("&") for k, v in[item.split("=")]}

                datas.append(row_data)
        self.wb.close()
        return datas

    def close(self):
        # 关闭文件流
        self.wb.close()

数据文档格式:

(这里目前只支持单 sheet 页读取,后续将扩展成 sheet 名称传参获取方式)

2、requests 请求部分

import requests
class HttpRequest:

    def __init__(self):
        self.session = requests.sessions.session()
        self.global_headers = {"X-Request-Sign": "xxxxxxxxx"}

    def set_headers(self, path, content_type, headers):
        # 判断接口是登录时,将Authorization请求头删除
        if path in ["oauth/oauth/userlogin", "/oauth/oauth/login"] and "Authorization" in self.global_headers.keys():
            self.global_headers.pop("Authorization")

        # 判断content_type为json时,添加请求头content_type
        if content_type == "json":
            self.global_headers["Content-Type"] = "application/json;charset=UTF-8"
        # 判断content_type不为json时,删除请求头content_type
        elif content_type != "json" and "Content-Type" in self.global_headers.keys():
            self.global_headers.pop("Content-Type")

        # 将接口信息中的请求头更新到global_headers中
        if headers:
            self.global_headers.update(headers)

    def set_token(self, path, res_code, res_json):
        # 请求接口为登录并且返回成功时,将token附加到headers里
        if path == "/oauth/oauth/login" and res_code == 200:
            token = json_path(res_json, "$.data.access_token")
            self.global_headers["Authorization"] = f"Bearer {token}"

    def clear_headers(self, headers):
        # 清除无用的请求头
        if headers:
            for i in headers.keys():
                if i in self.global_headers.keys():
                    self.global_headers.pop(i)

    def http_request(self, datas, file=None):
        log_info.log_info('--------------------')
        log_info.log_info('↓↓↓↓↓接口请求开始↓↓↓↓↓')

        # 拼接url  base从配置文件获取+参数传递接口路径
        urls = config.get_strValue('api', 'base_url') + datas["path"]

        self.set_headers(path=datas["path"], content_type=datas["content_type"], headers=datas["headers"])

        log_info.log_info(f'接口名称:{datas["desc"]}')
        log_info.log_info(f"请求地址:{urls}")
        log_info.log_info(f"请求头:{self.global_headers}")
        log_info.log_info(f'请求参数:{datas["params"]}')
        print((f"请求地址:{urls}"))
        res = None
        # 请求封装,这里用的session会话保持,https请求需要将verify设置为False
        try:
            if datas["method"].lower() == 'get':
                res = self.session.request(datas["method"], urls, params=datas["params"], headers=self.global_headers,
                                           verify=False)
            elif datas["method"].lower() == 'post':
                if datas["content_type"] in ["form", "upload"]:
                    res = self.session.request(datas["method"], urls, data=datas["params"], headers=self.global_headers,
                                               files=file,verify=False)
                elif datas["content_type"] == "json":
                    res = self.session.request(datas["method"], urls, json=datas["params"], headers=self.global_headers,
                                               verify=False)

            log_info.log_info(f"响应信息:{res.text}")
        except Exception as e:
            log_info.log_info(e)
            raise e

        self.set_token(path=datas["path"], res_code=res.status_code, res_json=res.json())
        self.clear_headers(headers=datas["headers"])

        log_info.log_info('↑↑↑↑↑接口请求结束↑↑↑↑↑')
        log_info.log_info('--------------------')
        return res

    def close(self):
        self.session.close()

在这里我根据项目情况,进行了请求头的特殊处理;
①实例化对象的时候添加了全局请求头
②根据不同类型的请求和传参格式对 content-type 做了相应处理
③token 的处理
④最后请求后把当前无用请求头部分做了清理

3、接口数据处理

class DataHandle:

    def datas_init(self,datas,params_list):
        self.datas = datas
        self.params_list = params_list

    def headers_handle(self):
        # 将请求头进行参数化
        if self.datas["headers"]:
            self.datas["headers"] = re_replace(self.datas["headers"], self.params_list)
        # 请求头转字典格式
        if self.datas["headers"]:
            self.datas["headers"] = {k: v for item in self.datas["headers"].split("&") for k, v in [item.split("=", 1)]}
        return self.datas["headers"]

    def params_handle(self):
        # 将请求参数进行参数化
        if self.datas["params"]:
            self.datas["params"] = re_replace(self.datas["params"], self.params_list)

        # 这里把请求参数格式化,转成不同方法需要的参数格式
        # 将form表单格式的参数转化成字典
        if self.datas["content_type"] in ["form","upload"] and self.datas["params"]:
            self.datas["params"] = {k: v for item in self.datas["params"].split("&") for k, v in [item.split("=")]}
        # 将json字符串解包为字典
        elif self.datas["content_type"] == "json":
            self.datas["params"] = eval(self.datas["params"])

        return self.datas["params"]

    def user_var_handle(self,res):
        # 获取响应信息中下文接口用到的参数
        if self.datas["json_path"]:
            for i in self.datas["json_path"].keys():
                self.params_list[i] = json_path(res.json(), self.datas["json_path"][i])

        return self.params_list

    def assert_handle(self,res):
        # 将预期结果处理
        if self.datas["except"]:
            # 参数化替换
            self.datas["except"] = re_replace(self.datas["except"], self.params_list)
            # 转成字符串列表
            self.datas["except"] = self.datas["except"].split("&")

        # 断言
        for i in self.datas["except"]:
            print(f"断言:{i}")
            assert i in res.text

    def send_request(self,datas,file=None):
        res = http_request.http_request(datas,file)
        return res

这里主要做了几点处理:
①将请求头部分做了参数化替换并进行了数据格式转换,转为字典格式
②请求 body 进行参数化,并根据请求方式和 content-type 做了相应处理
③用户参数处理,用 jsonpath 获取响应信息字段值存放到测试类属性中
④预期结果断言处理(这里统一用的自带的 assert 方法,还没进行二次封装)
⑤将更新后的 data 发送请求

4、测试类,测试方法

class TestDemo(DataHandle):
    params_list = {'custName': unique_string(), "custCertiNo": random_string(),
                   "custLegalPersonCertiNo": random_string(18), "custBankAccount": random_string(18),
                   "managerStamp":{"file": ("testpicture.png", open(upload_file + "\\" + "testpicture.png", "rb"), "image/png")}
                   }

    @ pytest.mark.usefixtures("login_guanliren")
    @ pytest.mark.parametrize("datas", DoExcel("demo").get_data_from_excel())
    def test_case(self, datas):
        # 数据初始化
        self.datas_init(datas, self.params_list)
        # 请求头处理
        datas["headers"] = self.headers_handle()
        # 请求参数处理
        datas["params"] = self.params_handle()
        # 获取文件名
        filename = datas["filename"]
        # 发送接口请求
        res = self.send_request(datas=datas,file=self.params_list[filename] if datas["content_type"] == "upload" else None)
        # 参数列表处理
        self.params_list = self.user_var_handle(res)
        # 预期结果及断言
        self.assert_handle(res)

这里测试类继承了上面的 DataHandle 类,测试方法中可以调用父类方法进行测试数据的处理
params_list 是用字典存放的参数化的数据,我这里是通过正则替换把请求参数里面的需要被替换的部分找到 params_list 里的 key,将 value 进行替换

5、runner 执行测试用例

import pytest
from project.utils.base_dir import *

if __name__ == '__main__':
    pytest.main(['-vs', f'{test_class}', f'--alluredir={allure_dir} ', '--clean-alluredir'])
    os.system(f"allure generate {allure_dir} -o {allure_html} --clean")

调用 pytest 的 main 方法进行命令行参数执行,添加了 allure 生成测试报告

6、测试报告展示

7、未完成功能

①excel 读取:现在只支持当前 sheet 页读取,后续扩展成传 sheet 名称指定获取
②数据库断言:excel 添加一列存放 sql 语句,发送接口请求后查询数据库,将这部分加进断言里
③http_requests 目前只有 get 和 post 方法,后续将其他方法也封装进去
④断言方式:目前没有对断言封装,用的自带的 assert 函数,后面可以进行二次封装或用第三方库实现

最后希望可以给一些想要入手自动化的同学们一些参考吧。

总结

 感谢每一个认真阅读我文章的人!!!

那么在这里我也精心准备了软件测试、自动化测试的详细资料包含:电子书,简历模块,各种工作模板,面试宝典,自学项目等。需要的点击下方名片加入群聊与我一起学习交流。谢谢大家。

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

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

相关文章

PhpStorm下载、安装、配置教程

前面的文章中,都是把.php文件放在WampServer的www目录下,通过浏览器访问运行。这篇文章就简单介绍一下PhpStorm这个php集成开发工具的使用。 目录 下载PhpStorm 安装PhpStorm 配置PhpStorm 修改个性化设置 修改字符编码 配置php的安装路径 使用Ph…

MySQL 常用数据类型总结

面试: 为什么建表时,加not null default ‘’ / default 0 答:不想让表中出现null值. 为什么不想要的null的值 答:(1)不好比较,null是一种类型,比较时,只能用专门的is null 和 is not null来比较. 碰到运算符,一律返回null &#xff08…

【Spring】06 生命周期之销毁回调

文章目录 1. 回调是什么2. 销毁回调2.1 实现 DisposableBean 接口2.2 配置 destroy-method 3. 执行顺序4. 应用场景总结 在 Spring 框架中,生命周期回调(Lifecycle Callbacks)是一种强大的机制,它允许我们在 Spring 容器中的 Bean…

Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

文章目录 引言MapperFactoryBean的用法和优缺点MapperScannerConfigurer的用法和优缺点MapperFactoryBean源码分析MapperScannerConfigurer源码分析Spring容器初始化流程回顾核心方法:postProcessBeanDefinitionRegistryBeanDefinitionRegistryPostProcessor和BeanF…

杰理-音箱-flash配置

杰理-音箱-flash配置 注意配置io,双线或者4线的硬件连接方式,否则无法烧录UI资源

STM32与Freertos入门(七)信号量

1、简介 FreeRTOS提供了二值信号(Binary Semaphore)作为一种同步机制,用于在任务之间进行简单的通信和同步操作。二值信号是一种特殊类型的信号量,只能有两种状态:0(未触发)和1(已触…

PAT 乙级 1019 数字黑洞

解法思路,我用c语言和python 做了这道题,这里面有一个小坑就是没说一定是4位整数,有可能是3位,2,1,位,用python排序时候需要注意,我c语言用的hash反而无所谓。。代码如下: c语言代码: #includ…

CTFHub | 反射型

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习,实训平台。提供优质的赛事及学习服务,拥有完善的题目环境及配套 writeup ,降低 CTF 学习入门门槛,快速帮助选手成长,跟随主流比赛潮流。 0x01 题目描述…

腾讯云服务器优惠活动大全页面_全站搜优惠合集

腾讯云推出优惠全站搜页面 https://curl.qcloud.com/PPrF9NFe 在这个页面可以一键查询所需云服务器、轻量应用服务器、数据库、存储、CDN、网络、安全、大数据等云产品优惠活动大全,活动打开如下图: 腾讯云优惠全站搜 腾讯云优惠全站搜页面 txybk.com/go…

STP笔记总结

STP --- 生成树协议 STP(Spanning Tree Protocol,生成树协议)是根据 IEEE802.1D标准建立的,用于在局域网中消除数据链路层环路的协议。运行STP协议的设备通过彼此交互信息发现网络中的环路,并有选择地对某些端口进行阻…

MyBatis原理解读

我们项目中多用MyBatis进行数据库的读写,开源的MyBatis-Plus框架对其进行了增强,使用上更加简单,我们之前的很多项目也是直接用的MyBatis-Plus。 数据库操作的时候,简单的单表读写,我们可以直接在方法里链式组装SQL,复杂的SQL或涉及多表联合join的,需要在xml手写SQL语句…

缓存的定义及重要知识点

文章目录 缓存的意义缓存的定义缓存原理缓存的基本思想缓存的优势缓存的代价 缓存的重要知识点 缓存的意义 在互联网高访问量的前提下,缓存的使用,是提升系统性能、改善用户体验的唯一解决之道。 缓存的定义 缓存最初的含义,是指用于加速 …

Python导入模块,Python import用法(超级详细)

对于一个真实的 Python 程序,我们不可能自己完成所有的工作,通常都需要借助于第三方类库。此外,也不可能在一个源文件中编写整个程序的源代码,这些都需要以模块化的方式来组织项目的源代码。 使用 import 导入模块的语法&#xf…

解决:TypeError: write() argument must be str, not tuple

解决:TypeError: write() argument must be str, not tuple 文章目录 解决:TypeError: write() argument must be str, not tuple背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时,报错&…

提供一个数据库的表,然后,分页显示表中所有信息,一页10个,此表130条信息。最后再以饼图显示男 女 未知 人数的情况。

运行之后显示的效果&#xff1a; 如果是新项目&#xff0c;建立项目后&#xff0c;把mysql驱动放到指定的目录下即&#xff1a; WebContent\WEB-INF-lib 我用的驱动是 mysql-connector-j-8.0.33.jar 展示页 listpage.jsp <%page import"java.util.Map.Entry"%&g…

DIY电脑装机机箱风扇安装方法

作为第一次自己diy一台电脑主机的我&#xff0c;在经历了众多的坑中今天来说一下如何安装机箱风扇的问题 一、风扇的数量 1、i3 xx50显卡 就用一个cpu散热风扇即可 2、i5 xx60 一个cpu散热风扇 一个风扇即可 3、i7 xx70 一个cpu散热 4个风扇即可 4、i9 xx80 就需要7个以…

设计模式详解---策略模式

1. 策略模式简介 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;用于在运行时根据不同的情境选择不同的算法或策略。该模式将算法封装成独立的类&#xff0c;使得它们可以相互替换&#xff0c;而且可以独立于客户端使用它们的方式。 1.1.…

拷贝的艺术:深拷贝与浅拷贝的区别与应用(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

轻量封装WebGPU渲染系统示例<50>- Json数据描述材质等场景信息

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/DataDrivenScene2.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下: json场景数据: {"renderer": {"mtplE…

设计模式——组合模式(结构型)

引言 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能像使用独立对象一样使用它们。 问题 如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 例如&#xff0c; 你有两类对象&#xff1a; ​…