怎么基于Pytest+Requests+Allure实现接口自动化测试?

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生内存数据库 Tair,内存型 2GB
简介: 该文介绍了一个基于Python的自动化测试框架,主要由pytest、requests和allure构成,采用关键字驱动模式。项目结构分为六层:工具层(api_keyword)封装了如get、post的请求;参数层(params)存储公共参数;用例层(case)包含测试用例;数据驱动层(data_driver)处理数据;数据层(data)提供数据;逻辑层(logic)实现用例逻辑。代码示例展示了如何使用allure装饰器增强测试报告,以及如何使用yaml文件进行数据驱动。

一、整体结构

  • 框架组成:pytest+requests+allure
  • 设计模式:
  • 关键字驱动
  • 项目结构:
  • 工具层:api_keyword/
  • 参数层:params/
  • 用例层:case/
  • 数据驱动:data_driver/
  • 数据层:data/
  • 逻辑层:logic/

二、具体步骤及代码

1、工具层

将get、post等常用行为进行二次封装。

代码(api_key.py)如下:

python

复制代码

import allure
import json
import jsonpath
import requests

# 定义一个关键字类
class ApiKey:
    # 将get请求行为进行封装
    @allure.step("发送get请求")
    def get(self, url, params=None, **kwargs):
        return requests.get(url=url, params=params, **kwargs)

    # 将post请求行为进行封装
    @allure.step("发送post请求")
    def post(self, url, data=None, **kwargs):
        return requests.post(url=url, data=data, **kwargs)

    # 由于接口之间可能相互关联,因此下一个接口需要上一个接口的某个返回值,此处采用jsonpath对上一个接口返回的值进行定位并取值
    @allure.step("获取返回结果字典值")
    def get_text(self, data, key):
        # json数据转换为字典
        json_data = json.loads(data)
        # jsonpath取值
        value = jsonpath.jsonpath(json_data, '$..{0}'.format(key))
        return value[0]
  • 其中引用allure.step()装饰器进行步骤详细描述,使测试报告更加详细。
  • 使用jsonpath对接口的返回值进行取值。

2、数据层

数据采用yaml文件。

代码(user.yaml)如下:

python

复制代码

-
  user:
    username: admin
    password: '123456'
  msg: success
  title: 输入正确账号、密码,登录成功
-
  user:
    username: admin1
    password: '1234561'
  msg: 用户名或密码错误
  title: 输入错误账号1、密码1,登录失败
-
  user:
    username: admin2
    password: '1234562'
  msg: 用户名或密码错误
  title: 输入错误账号2、密码2,登录失败
  • 其中title是为了在用例进行时动态获取参数生成标题。

3、数据驱动层

对数据进行读写。

代码(yaml.driver.py)如下:

python

复制代码

import yaml


def load_yaml(path):
    file = open(path, 'r', encoding='utf-8')
    data = yaml.load(file, Loader=yaml.FullLoader)
    return data

4、参数层

参数层存放公共使用的参数,在使用时对其进行调用。

代码(allParams.py)如下:

python

复制代码

'''python
    规则:
    全局变量使用大写字母表示
'''

# 地址
URL = 'http://39.98.138.157:'

# 端口
PORT = '5000'

5、逻辑层

用例一:进行登录的接口请求,此处登录请求在yaml文件里设置了三组不同的数据进行请求。

用例二:进行个人查询的接口请求,此处需要用到登录接口返回的token值。

用例三、进行添加商品到购物车的接口请求,此处需要用到登录接口返回的token值以及个人查询接口返回的openid、userid值

用例四、进行下单的接口请求,此处需要用到登录接口返回的token值以及个人查询接口返回的openid、userid、cartid值

注意:由于多数接口需要用到登录接口返回的token值,因此封装一个conftest.py定义项目级前置fixture,在整个项目只执行一次,可以在各个用例中进行调用(其他共用参数也可以采取类似前置定义)。同时由于此处定义的项目级fixture,因此可以将初始化工具类ak = ApiKey()也放入其中。

代码(conftest.py)如下:

python

复制代码

from random import random

import allure
import pytest

from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


def pytest_collection_modifyitems(items):
    """
    测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上
    """
    for item in items:
        item.name = item.name.encode("utf-8").decode("unicode_escape")
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")


# 项目级fix,整个项目只初始化一次
@pytest.fixture(scope='session')
def token_fix():
    # 初始化工具类
    ak = ApiKey()
    with allure.step("发送登录接口请求,并获取token,整个项目只生成一次"):
        # 请求接口
        # url = 'http://39.98.138.157:5000/api/login'
        url = URL + PORT + '/api/login'
        # 请求参数
        userInfo = {
            'username': 'admin',
            'password': '123456'
        }
        # post请求
        res = ak.post(url=url, json=userInfo)
        # 获取token
        token = ak.get_text(res.text, 'token')
        # 验证代码,验证token只生成一次
        token_random = random()
        return ak, token, res, token_random
  • 其中也包含了防止中文乱码,加入了pytest_collection_modifyitems(函数)。

设置好conftest后,就可以应用在逻辑层里面了。

代码(shopingApi.py)如下:

python

复制代码

import pytest
import allure
from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


class ApiCase():
    # 登录逻辑
    def params_login(self, userdata):
        # 动态获取参数生成标题
        allure.dynamic.title(userdata['title'])
        # 初始化工具类
        ak = ApiKey()
        # 请求接口
        url = URL + PORT + '/api/login'
        # 请求参数
        userInfo = {
            'username': userdata['user']['username'],
            'password': userdata['user']['password']
        }
        res = ak.post(url=url, json=userInfo)
        with allure.step("接口返回信息校验及打印"):
            print("/api/login登录接口请求响应信息")
            print(res.text)
            # 获取响应结果
            msg = ak.get_text(res.text, 'msg')
            print(msg)
            # 断言
            assert msg == userdata['msg']

    def params_getuserinfo(self, token_fix):
        # 从fix中获取预置的工具类和token,所有返回值都需要接收
        ak, token, res, token_random01 = token_fix
        with allure.step("发送个人查询接口请求"):
            url = URL + PORT + '/api/getuserinfo'
            headers = {
                'token': token
            }
            res1 = ak.get(url=url, headers=headers)
        with allure.step("接口返回信息校验及打印"):
            print("/api/getuserinfo个人用户查询接口请求响应信息")
            print(res1.text)
            # print("验证的random值,测试用")
            # print(token_random01)
            name = ak.get_text(res1.text, 'nikename')
            # 断言
            assert "风清扬" == name
        return res1

    def params_addcart(self, token_fix):
        # 从fix中获取预置的工具类和token
        # 所有返回都要获取,不然会报错
        ak, token, res, token_random01 = token_fix
        with allure.step("调用getuserinfo接口获取返回信息"):
            res1 = self.params_getuserinfo(token_fix)
        with allure.step("发送添加商品到购物车请求"):
            # 添加商品到购物车,基于token、userid、openid、productid
            url = URL + PORT + '/api/addcart'
            hd = {
                "token": token
            }
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888
            }
            # 发送请求
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("接口返回信息校验及打印"):
            print("/api/addcart添加商品到购物车请求响应信息")
            print(res2.text)
            # print("验证的random值,测试用")
            # print(token_random01)
            result = ak.get_text(res2.text, 'result')
            assert 'success' == result
        return res2

    def params_createorder(self, token_fix):
        ak, token, res, token_random01 = token_fix
        with allure.step("调用addcart接口获取返回信息"):
            res1 = self.params_addcart(token_fix)
        with allure.step("发送下单请求"):
            url = URL + PORT + '/api/createorder'
            # 从项目级fix中获取token
            hd = {
                "token": token
            }
            # 从添加商品到购物车接口中获取userid,openid,cartid
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888,
                "cartid": ak.get_text(res1.text, 'cartid')
            }
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("接口返回信息校验及打印"):
            print("/api/createorder下单请求响应信息")
            print(res2.text)
            # print("验证的random值,测试用")
            # print(token_random01)
            result = ak.get_text(res1.text, 'result')
            assert 'success' == result

**6、用例层

**调用逻辑层进行用例管理和数据传输。

代码(test_Tree.py)如下:

python

复制代码

import allure
import pytest
from pytest_demo_2.data_driver import yaml_driver
from pytest_demo_2.logic.shopingApi import ApiCase


@allure.epic("shopXo电商平台接口-接口测试")
class TestTree():
    # 初始化用例库
    actions1 = ApiCase()

    @allure.feature("01.登陆")
    @allure.story("02.一般场景")
    @pytest.mark.parametrize('userdata', yaml_driver.load_yaml('./data/user.yaml'))
    def test_case01(self, userdata):
        self.actions1.params_login(userdata)

    @allure.feature("02.个人查询")
    @allure.story("01.典型场景")
    @allure.title("个人查询")
    def test_case02(self, token_fix):
        self.actions1.params_getuserinfo(token_fix)

    @allure.feature("03.添加商品到购物车")
    @allure.story("01.典型场景")
    @allure.title("添加商品到购物车")
    def test_case03(self, token_fix):
        self.actions1.params_addcart(token_fix)

    @allure.feature("04.下单")
    @allure.story("01.典型场景")
    @allure.title("下单")
    def test_case04(self, token_fix):
        self.actions1.params_createorder(token_fix)

**7、运行

**代码(main_run.py)如下:

python

复制代码

import os
import pytest


def run():
    pytest.main(['-v', './case/test_Tree.py',
                 '--alluredir', './result', '--clean-alluredir'])
    os.system('allure serve result')
    # os.system('allure generate ./result/ -o ./report_allure/ --clean')

if __name__ == '__main__':
    run()


转载来源:https://juejin.cn/post/7358336719166275636

相关文章
|
2天前
|
Shell Python
`pytest-httpserver`是一个pytest插件,它允许你在测试期间启动一个轻量级的HTTP服务器,并模拟HTTP请求和响应。
`pytest-httpserver`是一个pytest插件,它允许你在测试期间启动一个轻量级的HTTP服务器,并模拟HTTP请求和响应。
18 4
|
2天前
|
监控 Python
`pytest-qt` 是一个用于在 Qt 应用程序中进行 GUI 测试的 pytest 插件。
`pytest-qt` 是一个用于在 Qt 应用程序中进行 GUI 测试的 pytest 插件。
16 3
|
24天前
|
Java 测试技术 Python
《手把手教你》系列基础篇(八十)-java+ selenium自动化测试-框架设计基础-TestNG依赖测试-番外篇(详解教程)
【6月更文挑战第21天】本文介绍了TestNG中测试方法的依赖执行顺序。作者通过一个实际的自动化测试场景展示了如何设计测试用例:依次打开百度、搜索“selenium”、再搜索“selenium+java”。代码示例中,`@Test`注解的`dependsOnMethods`属性用于指定方法间的依赖,确保执行顺序。如果不设置依赖,TestNG会按方法名首字母排序执行。通过运行代码,验证了依赖关系的正确性。
41 4
|
1天前
|
存储 Web App开发 Java
《手把手教你》系列基础篇(九十五)-java+ selenium自动化测试-框架之设计篇-java实现自定义日志输出(详解教程)
【7月更文挑战第13天】这篇文章介绍了如何在Java中创建一个简单的自定义日志系统,以替代Log4j或logback。
12 5
|
4天前
|
设计模式 测试技术 Python
《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
【7月更文挑战第10天】Page Object Model (POM)是Selenium自动化测试中的设计模式,用于提高代码的可读性和维护性。POM将每个页面表示为一个类,封装元素定位和交互操作,使得测试脚本与页面元素分离。当页面元素改变时,只需更新对应页面类,减少了脚本的重复工作和维护复杂度,有利于团队协作。POM通过创建页面对象,管理页面元素集合,将业务逻辑与元素定位解耦合,增强了代码的复用性。示例展示了不使用POM时,脚本直接混杂了元素定位和业务逻辑,而POM则能解决这一问题。
23 6
|
2天前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
11 2
|
6天前
|
XML Java 测试技术
《手把手教你》系列基础篇(九十一)-java+ selenium自动化测试-框架设计基础-Logback实现日志输出-下篇(详解教程)
【7月更文挑战第9天】在Java项目中,使用Logback配置可以实现日志按照不同包名输出到不同的文件,并且根据日志级别分开记录。
20 4
|
7天前
|
XML Java 测试技术
《手把手教你》系列基础篇(九十)-java+ selenium自动化测试-框架设计基础-Logback实现日志输出-中篇(详解教程)
【7月更文挑战第8天】这篇教程介绍了如何使用Logback将Java应用的日志输出到文件中。首先,通过创建`logback.xml`配置文件,设置`FileAppender`来指定日志文件路径和格式。然后,提供了一个`RollingFileAppender`的例子,用于每日生成新的日志文件并保留一定天数的历史记录。文中包含配置文件的XML代码示例,并展示了控制台输出和生成的日志文件内容。教程最后提到了一些可能遇到的问题及解决建议。
15 0
《手把手教你》系列基础篇(九十)-java+ selenium自动化测试-框架设计基础-Logback实现日志输出-中篇(详解教程)
|
12天前
|
XML 测试技术 数据格式
《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)
【7月更文挑战第3天】TestNG教程展示了如何自定义日志记录。首先创建一个名为`TestLog`的测试类,包含3个测试方法,其中一个故意失败以展示日志。使用`Assert.assertTrue`和`Reporter.log`来记录信息。接着创建`CustomReporter`类,继承`TestListenerAdapter`,覆盖`onTestFailure`, `onTestSkipped`, 和 `onTestSuccess`,在这些方法中自定义日志输出。
27 6
|
8天前
|
Java 关系型数据库 测试技术
《手把手教你》系列基础篇(八十九)-java+ selenium自动化测试-框架设计基础-Logback实现日志输出-上篇(详解教程)
【7月更文挑战第7天】Apache Log4j2的安全漏洞促使考虑使用logback作为替代的日志框架。Logback由log4j创始人设计,提供更好的性能,更低的内存使用,并且能够自动重载配置文件。它分为logback-core、logback-classic(实现了SLF4J API)和logback-access(用于Servlet容器集成)三个模块。配置涉及Logger、Appender(定义日志输出目的地)和Layout(格式化日志)。
15 1