一个简单的接口自动化测试框架:Python+Requests+Pytest+Allure

news2025/1/12 20:41:39

项目结构

project:api_test
——api_keyword
————api_key.py:接口关键字驱动类
——case
————test_cases.py:测试套件和测试用例
——report_allure(无需创建):allure报告
——result(无需创建):测试用例运行结果
——VAR
————VAR.py:常量类
conftest.py:项目级别fixture
main.py:主函数

1. api_key.py

getattr和eval在接口测试中的应用场景,封装一个接口关键字驱动类ApiKey,作为一个基类,是整个框架的核心,用于提供自动化接口测试的关键字方法。

  1. 各种模拟请求方法:post/get/put/delete/header/…
  2. 集成Allure时,可添加@allure.step,这样在自动化执行的时候 allure报告可以直接捕捉相关的执行信息,让测试报告信息更详细
  3. 进行断言封装

代码实现

import json
import allure
import jsonpath
import requests
import pymysql
import hashlib
import time
from Crypto.Cipher import AES
import base64
import rsa


class ApiKey:
    # get请求的封装:因为params可能存在无值的情况,存放默认None
    @allure.step("发送get请求")
    def get(self, url, params=None, **kwargs):
        return requests.get(url=url, params=params, **kwargs)

    @allure.step("发送post请求")
    # post请求的封装:data也可能存在无值得情况,存放默认None
    def post(self, url, data=None, **kwargs):
        return requests.post(url=url, data=data, **kwargs)

    @allure.step("获取返回结果字典值")
    # 基于jsonpath获取数据的关键字:用于提取所需要的内容
    def get_text(self, data, key):
        # jsonpath获取数据的表达式:成功则返回list,失败则返回false
        # loads是将json格式的内容转换为字典的格式
        # jsonpath接收的是dict类型的数据
        dict_data = json.loads(data)
        value = jsonpath.jsonpath(dict_data, key)
        if isinstance(value, list):
            return value[0]
        else:
            return value

    @allure.step("断言实际结果等于预期结果")
    def my_assert(self, acutal, expect):
        try:
            assert acutal == expect
        except:
            return "断言失败"
        else:
            return "断言成功"

    # 数据库检查
    @allure.step("数据库检查参数")
    def sqlCheck(self, sql, n):
        conn = pymysql.connect(
            host='shop-xo.hctestedu.com',
            port=3306,
            user='api_test',
            passwd='Aa9999!',
            database='shopxo_hctested',
            charset='utf8')
        # 创建游标
        cmd = conn.cursor()
        # 准备并执行sql语句
        cmd.execute(query=sql)
        # 获取n条查询结果
        results = cmd.fetchmany(n)[0][0]
        conn.close()
        return results

    @allure.step("Md5加密")
    def enMd5(self, text):
        # 获取变量的内存地址,获取加密后的密文值
        return hashlib.md5(text.encode('utf-8')).hexdigest()

    # AES加密填充使用
    def pad(self, text):
        """
        #填充函数,使被加密数据的字节码长度是block_size的整数倍
        """
        length = AES.block_size  # 初始化数据块大小
        count = len(text.encode('utf-8'))
        add = length - (count % length)
        entext = text + (chr(add) * add)
        return entext

    @allure.step("AES加密")
    def enAES(self, key, text):
        global aes
        key = key.encode("utf-8")  # 初始化密钥
        aes = AES.new(key, AES.MODE_ECB)  # 初始化AES,ECB模式的实例,可以选择其他模式
        res = aes.encrypt(self.pad(text).encode("utf8"))
        # Base64是网络上最常见的用于传输8Bit字节码的编码方式之一
        msg = str(base64.b64encode(res), encoding="utf8")
        return msg

    @allure.step("AES解密")
    def deAES(self, text):
        # 截断函数,去除填充的字符
        unpad = lambda date: date[0:-ord(date[-1])]
        res = base64.decodebytes(text.encode("utf8"))
        msg = aes.decrypt(res).decode("utf8")
        return unpad(msg)

    # 秘钥的位数, 可以自定义指定, 例如: 128、256、512、1024、2048等
    @allure.step("生成RSA公钥和私钥")
    def keyRSA(self, num):
        (pubkey, privkey) = rsa.newkeys(num)
        # 生成公钥
        pub = pubkey.save_pkcs1()
        with open('public.pem', 'wb') as f:
            f.write(pub)

        # 生成私钥
        pri = privkey.save_pkcs1()
        with open('private.pem', 'wb') as f:
            f.write(pri)

    @allure.step("RSA加密")
    def enRSA(self, text):
        # 以 utf-8 的编码格式打开指定文件
        f = open("public.pem", encoding="utf-8")
        # 输出读取到的数据
        pub_str = f.read()
        # 关闭文件
        f.close()
        pub_key = rsa.PublicKey.load_pkcs1(pub_str)
        # rsa加密 最后把加密字符串转为base64
        text = text.encode("utf-8")
        cryto_info = rsa.encrypt(text, pub_key)
        cipher_base64 = base64.b64encode(cryto_info)
        cipher_base64 = cipher_base64.decode()
        return cipher_base64

    @allure.step("RSA解密")
    def deRSA(self, text):
        # 以 utf-8 的编码格式打开指定文件
        f = open("private.pem", encoding="utf-8")
        # 输出读取到的数据
        priv_str = f.read()
        # 关闭文件
        f.close()
        priv_key = rsa.PrivateKey.load_pkcs1(priv_str)
        # rsa解密 返回解密结果
        cryto_info = base64.b64decode(text)
        talk_real = rsa.decrypt(cryto_info, priv_key)
        res = talk_real.decode("utf-8")
        return res

    @allure.step("获取签名")
    def getsign(self):  # 获取老签名
        dealkey = [0x07, 0xB6, 0x79, 0x56, 0x7A, 0x5C, 0x4A, 0xBE, 0x1D, 0xF1, 0xB2, 0x10, 0x3C, 0x5E, 0xDC, 0xA6,
                   0x56, 0xE7, 0x88, 0x25, 0x87, 0x95, 0xD5, 0x85, 0x76, 0x7D, 0xEA, 0x66, 0xF5, 0x0A, 0xC3, 0xA8,
                   0x55, 0x28, 0x67, 0x14, 0x06, 0xE7, 0xCB, 0x68, 0xAC, 0x2E, 0x00, 0x36, 0x57, 0x2F, 0xD2, 0xE2,
                   0x54, 0xE9, 0xC6, 0xA3, 0x03, 0xC6, 0x07, 0x33, 0xBD, 0xF1, 0x6D, 0x46, 0x62, 0xFD, 0x82, 0xCF,
                   0xA3, 0x50, 0x15, 0xB2, 0x53, 0xA4, 0x9C, 0x93, 0x98, 0x55, 0x8E, 0xF8, 0xC1, 0x0C, 0x15, 0x71,
                   0x42, 0x6A, 0xA4, 0xF1, 0x5D, 0x72, 0xB1, 0xC4, 0xF6, 0xF0, 0x56, 0xAE, 0xCA, 0x77, 0x44, 0x45,
                   0x21, 0x1B, 0x93, 0x40, 0x49, 0x89, 0x52, 0x76, 0x2C, 0x64, 0xB8, 0x3B, 0xF9, 0x8D, 0x51, 0xA5,
                   0x80, 0x2C, 0x92, 0x39, 0xF7, 0xAD, 0xAF, 0x59, 0x1F, 0x06, 0xDE, 0x5A, 0x1D, 0x91, 0x1C, 0xDB,
                   0x6F, 0xAD, 0xC1, 0xE8, 0xE5, 0xD4, 0xB4, 0x7C, 0x3E, 0x61, 0x73, 0x2D, 0xCE, 0xCD, 0x01, 0xDF,
                   0x5E, 0xCE, 0x60, 0xB7, 0x83, 0xD1, 0x39, 0xA9, 0xF3, 0x35, 0x05, 0xBA, 0x88, 0x78, 0x97, 0xFC,
                   0x3D, 0x2F, 0xF9, 0x36, 0x2A, 0x38, 0xB0, 0x25, 0x16, 0xA7, 0x08, 0x8C, 0xF6, 0x21, 0xC8, 0x22,
                   0xBC, 0x90, 0x48, 0x35, 0x9A, 0x0D, 0x1A, 0xD9, 0xFA, 0xCC, 0x70, 0xAA, 0x42, 0x3F, 0xB6, 0xE1,
                   0xBB, 0x41, 0x17, 0x74, 0xC2, 0x48, 0x7E, 0x80, 0xD6, 0x09, 0xC5, 0x24, 0x60, 0x30, 0x0E, 0xE3,
                   0xFA, 0x92, 0x66, 0x43, 0xE1, 0x8A, 0x4D, 0xD7, 0x1B, 0x6B, 0x23, 0x65, 0xA0, 0x12, 0x9D, 0x9B,
                   0xE0, 0x93, 0xE5, 0xD2, 0xE3, 0xF4, 0xDC, 0x41, 0xA4, 0x3A, 0x10, 0x2B, 0x96, 0xED, 0x1B, 0x1E,
                   0xA9, 0xB4, 0x34, 0x11, 0x94, 0xA6, 0x75, 0x34, 0xD8, 0x89, 0xFC, 0x4F, 0x3B, 0x22, 0xB1, 0xA7]
        # 生成13位整数时间戳
        timestamp = int(time.time() * 1000)
        str1 = str(timestamp) + str('_') + str(dealkey[timestamp % len(dealkey)])
        sign = hashlib.md5(str1.encode('utf-8')).hexdigest()
        return sign, timestamp


if __name__ == '__main__':
    ak = ApiKey()
    # MD5
    print("MD5加密:", ak.enMd5("测试同学"))

    # AES加密和解密
    print("AES加密:", ak.enAES("1234567812345678", "测试同学"))
    print("AES解密:", ak.deAES("CFbJUXkduezgDZ7ZbO+SOw=="))

    # RSA加密
    ak.keyRSA(512)
    print("RSA加密:", ak.enRSA("测试同学"))
    print("RSA解密:", ak.deRSA(ak.enRSA("测试同学")))

    timestamp = int(time.time() * 1000)
    print(timestamp)

2. VAR.py

常量统一管理文件,为了方便代码中识别,目录、文件、常量名全部大写。根据项目中的需要,添加常量到此py文件中。

当前示例用到的几个常量如下:

# 项目链接
PROJECT_URL = "http://shop-xo.hctestedu.com/index.php?s="
# 公共参数
PARAMS = {
    "application": "app",
    "application_client_type": "weixin"
}
# 用户名
USERNAME = "zz"
# 密码
PASSWD = "123456"

3. test_cases.py

此框架执行的测试用例,全部在py文件中编写。可以根据自己项目的需求,采取在excel编写用例的这种方式,借助openyxl对excel测试用例进行读取和处理。

用到了allure,配置及使用教程详见:
Pytest+html,Pytest+allure配置及使用

代码实现

以下给出了几个用例的示例

import allure
import pytest

from VAR.VAR import PARAMS, PROJECT_URL


@pytest.mark.skip
@allure.feature("用户注册")
@allure.title("test_register_001, 用户名为不超过7位,注册成功")
def test_register_001(token_fix):
    # 从fix中获取预置的工具类和token
    # 所有返回都要获取,不然会报错
    ak, token = token_fix
    data = {
        "accounts": "zz0010",
        "pwd": 123456,
        "type": "username"
    }

    url = PROJECT_URL + "api/user/reg"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 输出结果
    print(resp.json())
    # 结果断言
    msg = ak.get_text(resp.text, "$..msg")
    assert msg == "注册成功"


@allure.feature("用户注册")
@allure.title("test_register_002, 验证当type输入不存在的类型提示错误信息")
def test_register_002(token_fix):
    # 从fix中获取预置的工具类和token
    # 所有返回都要获取,不然会报错
    ak, token = token_fix
    data = {
        "accounts": "zz0010",
        "pwd": 123456,
        "type": "phone"
    }

    url = PROJECT_URL + "api/user/reg"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 输出结果
    print(resp.json())
    # 结果断言
    msg = ak.get_text(resp.text, "$..msg")
    assert msg == "注册类型有误"


@allure.feature("登录")
@allure.title("test_login_001, 使用用户名能正确的登录用户")
def test_login_001(token_fix):
    # 从fix中获取预置的工具类和token
    # 所有返回都要获取,不然会报错
    ak, token = token_fix
    data = {
        "accounts": "zz888",
        "pwd": 123456,
        "type": "username"
    }

    url = PROJECT_URL + "api/user/login"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 输出结果
    print(resp.json())
    # 结果断言
    msg = ak.get_text(resp.text, "$..msg")
    assert msg == "登录成功"


@allure.feature("登录")
@allure.title("test_login_002, 验证输入错误的用户名提示用户")
def test_login_002(token_fix):
    # 从fix中获取预置的工具类和token
    # 所有返回都要获取,不然会报错
    ak, token = token_fix
    data = {
        "accounts": "zz1231231231223",
        "pwd": 123456,
        "type": "username"
    }

    url = PROJECT_URL + "api/user/login"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 输出结果
    print(resp.json())
    # 结果断言
    msg = ak.get_text(resp.text, "$..msg")
    assert msg == "登录帐号不存在"


@allure.feature("登录")
@allure.title("test_login_003, 验证用户名为空提示用户")
def test_login_003(token_fix):
    # 从fix中获取预置的工具类和token
    # 所有返回都要获取,不然会报错
    ak, token = token_fix
    data = {
        "accounts": "",
        "pwd": 123456,
        "type": "username"
    }

    url = PROJECT_URL + "api/user/login"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 输出结果
    print(resp.json())
    # 结果断言
    msg = ak.get_text(resp.text, "$..msg")
    assert msg == "登录账号不能为空"

4. conftest.py

项目级fixture,整个项目只初始化一次。在这个示例中,所有测试用例使用的都是一个账号,因此账号信息放置VAR.py中。接口需要鉴权,因此执行每个接口之前,都需要先登录拿到token作为后续接口的参数,再进行相关测试,例如加入购物车、删除商品、查询订单等等。

import pytest
from VAR.VAR import USERNAME, PASSWD, PROJECT_URL, PARAMS
from api_keyword.api_key import ApiKey


# 项目级fixture,整个项目只初始化一次
@pytest.fixture(scope='session')
def token_fix():
    # 初始化工具类
    ak = ApiKey()
    data = {
        "accounts": USERNAME,
        "pwd": PASSWD,
        "type": "username"
    }
    url = PROJECT_URL + "api/user/login"
    resp = ak.post(url=url, params=PARAMS, json=data)
    # 获取token
    token = ak.get_text(resp.text, '$..token')
    return ak, token

5. main.py

import os
import pytest

if __name__ == '__main__':
    # 运行某个py文件下指定的testcase
    # pytest.main(['-v', '--alluredir', './result', '--clean-alluredir', './case/test_cases.py::test_login_001'])
    # 运行某个py文件下的所有测试用例
    pytest.main(['-v', '--alluredir', './result', '--clean-alluredir', './case/test_cases.py'])
    # 运行前清除之前旧的report
    os.system('allure generate ./result/ -o ./report_allure/ --clean')

6. 执行所有测试用例

运行main.py文件,将会运行所有测试用例,生成allure测试报告。main.py中的pytest命令,指明了testcase运行结果的目录和allure报告的路径。

所有测试用例运行完成后,会自动生成对应目录。report_allure和result。

7. 查看allure报告

allure从多个维度生成了测试报告,体验很不错。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8. 定时构建

CI/CD的方式,还可以:
使用Jenkins集成Python + Pytest + Allure

9. 框架优化

这个框架还有很多优化的空间~ 有什么好的idea,快去实践一下吧~

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

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

相关文章

git 如何撤销历史某次merge

git,如何 撤销某一次历史提交或merge,并保留该版本的后续提交? 场景1: 你有两个功能迭代版本的分支,一个是 15 号上线,一个是25号上线。5号的时候产品突然说,这两个版本一起上,然后…

【计算机图形学】NAP: Neural 3D Articulation Prior

文章目录 1. 这篇论文做了什么事,有什么贡献?2. Related Work铰接物体建模3D中的Diffusion model扩散模型 3. Pipeline铰接树参数化基于Diffusion的铰接树生成去噪网络 4. 实验评价铰接物体生成——以往做法与本文提出的新指标NAP捕捉到的铰接物体分布质…

gazebo卡住不动

可以看到这个sdf文件里面含有网络连接,有的sdf文件看上去几十个kb, 以为很小,但是里面含有网络连接就不知道有多大了,所以也可能会卡住,然后就会出现这个

无线路由器的五种工作模式:

1.Router,即无线路由模式,这也是我们最常用的一种工作方式,一般家里宽带连接,将宽带猫连接在无线路由的WAN口上,然后做拨号帐号设置,就用这个模式即可。 在Router(无线路由)模式下&a…

linux下docker搭建mysql8

1:环境信息 centos 7,mysql8 安装docker环境 2.创建mysql容器 2.1 拉取镜像 docker pull mysql:8.0.23 2.2 查询镜像拉取成功 docker images 2.3 创建挂载的目录文件 mkdir /usr/mysql8/conf mkdir /usr/mysql8/data ##给data文件赋予操作权限 chmod 777 /…

关于时间格式yyyy-M-d或yyyy-MM-d到yyyy-MM-dd的转换

工作时遇到前端传的时间格式是"2023-12-3 17:41:52",和"2023-1-1 17:41:52"但是我想要的是"2023-12-03 17:41:52"和"2023-01-01 17:41:52"。下面给大家分享几个解决方法 方法一: 找前端!让他改&…

TCP_可靠数据传输原理

引言 在网络通信中,TCP是确保数据可靠传输的关键协议。但在我们深入研究TCP拥塞控制技术之前,让我们先探索可靠数据传输的原理,特别是TCP头部中一些重要字段的作用。 网络层提供了点对点的通信服务,努力交付数据报,但…

[概率论]四小时不挂猴博士

贝叶斯公式是什么 贝叶斯公式是概率论中的一个重要定理,用于计算在已知一些先验信息的情况下,更新对事件发生概率的估计。贝叶斯公式的表达式如下: P(A|B) P(B|A) * P(A) / P(B) 其中,P(A|B)表示在事件B发生的条件下事件A发生的概…

Android App从备案到上架全过程

不知道大家注意没有,最近几年来,新的移动App想要上架是会非常困难的,并且对于个人开发者和小企业几乎是难如登天,各种备案和审核。但是到底有多难,或许只有上架过的才会有所体会。 首先是目前各大应用市场陆续推出新的声明,各种备案截止日期到12月就要到最后期限责令整改…

wps将姓名处理格式为:姓**

1.打开wps,在要处理数据右侧一个单元格 输入公式:LEFT(A1,1)&"**",然后回车 2.按住ctrl和处理好的数据的右下角小方框,往下拖动即可生成格式为:姓** 格式的数据 3.复制生成的数据,右键选择 “…

三、HTML元素

一、HTML元素 HTML 文档由 HTML 元素定义。 *开始标签常被称为起始标签(opening tag),结束标签常称为闭合标签(closing tag)。 二、HTML 元素语法 HTML 元素以开始标签起始。HTML 元素以结束标签终止。元素的内容是…

Zero-shot:半监督:pansharpening

Zero-shot semi-supervised learning for pansharpening (用于全色锐化的零次半监督学习) 全色锐化是指融合低分辨率多光谱图像(LRMS)和高分辨率全色(PAN)图像以生成高分辨率多光谱图像(HRMS&…

履机乘变,轻舟便楫:源启分布式PaaS深度赋能企业级技术平台建设

导语 源启分布式PaaS平台围绕应用视角为用户提供应用运行的全生命周期管控能力,提供注册中心、服务路由、网关、服务治理等中间件技术支持,实现应用之间的联通,解决客户多厂商产品不兼容、产品组合不可选择、孤岛效应等问题,满足…

CSS animation动画和关键帧实现轮播图效果HTML

CSS animation动画和关键帧实现轮播图效果HTML 这轮播图效果使用h5和css3实现效果&#xff0c;不需要js控制&#xff0c;但是其中的缺点就是不能使用鼠标进行切换效果。 具有代码如下 <!DOCTYPE html> <html lang"en"><head><meta charset&quo…

MACOS Atrust服务异常

MAC版Atrust服务异常 点击进入办公后出现提示其一&#xff1a; 核心服务未启动&#xff0c;部分功能存在异常&#xff0c;确定重新启动吗&#xff1f; 可能的原因&#xff1a; 1.上次已完全退出客户端 2.核心服务被其他程序优化禁用 点击重新启动后&#xff0c;出现提示&#x…

M3u8视频地址如何转为mp4视频

在当今数字化的时代&#xff0c;视频格式的转换已成为日常需求。M3u8格式的视频由于其分段的特性&#xff0c;常常给播放和编辑带来不便。而MP4格式则因其通用性和高质量而广受欢迎。那么&#xff0c;如何将M3u8视频地址转换为MP4格式呢&#xff1f;接下来&#xff0c;我们将为…

iOS 小组件开发

iOS14之后Apple引入了新的WidgetKit&#xff0c;舍弃了原有额TodayExtension。 开发准备&#xff1a; 新的WidgetExtension只能通过SwiftUI进行开发&#xff1b; Widget有三种尺寸&#xff1a;systemSmall、 systemMedium、systemLarge&#xff0c;三种尺寸对应固定的UI类型布…

从0创建springboot项目并创建GitHub仓库

创建springboot项目 我是使用idea来创建一个新的项目&#xff0c;jdk17&#xff0c;maven。 先选一个spring web就够了&#xff0c;后续需要啥功能再慢慢添加 写个简单controller测试一下能不能成功启动项目 启动项目&#xff0c;本地访问http://localhost:8080/test/test …

polar CTF web 被黑掉的站

一、题目 二、解答 1、dirsearch 扫目录 看到shell.php和index.php.bak 一看就是爆破&#xff0c;字典都给了&#xff0c;最后得到为 nikel

C++数据结构-广义表

广义表的定义 如果允许表中的数据元素具有自身结构&#xff0c;即数据元素也可以是一个线性表&#xff0c;这就是广义表&#xff0c;有时也称之为列表&#xff08;Lists&#xff09;。 广义表是n(n≥0)个元素a1, a2, …, an的有限序列&#xff0c;即LS(a1, a2, …, an)。 其…