当前位置: 首页 > article >正文

基于pytest+requests+allure+yaml实现接口自动化测试框架

1.项目背景和目标

这是一个基于Python的接口自动化测试框架,主要目标是:

- 提供一个稳定、可维护的接口测试解决方案

- 实现测试用例与测试数据的解耦

- 提供详细的测试报告和日志记录

- 支持多环境配置和灵活的用例管理

2.技术栈选型

框架采用了以下核心技术:

pytest:作为测试执行引擎,提供了强大的用例管理和参数化能力

requests:处理HTTP请求,支持各种接口调用场景

allure:生成美观的测试报告,支持详细的测试步骤记录

yaml:管理配置文件和测试数据,提供更好的可读性和维护性

logging:实现完整的日志记录系统

3.项目分层架构设计

让我详细介绍这个项目的分层架构设计:

  • 测试用例层 (testcases/)

    - test_api.py:测试用例实现

      - 用例编写和管理

      - 测试流程控制

      - 测试数据参数化

      - 断言结果验证

import pytest
import allure
import json
import time
from datetime import datetime
from common.http_client import HTTPClient
from common.assertions import Assertions
from common.yaml_handler import YamlHandler
from common.variable_handler import VariableHandler

class TokenManager:
    def __init__(self):
        self.token = None
        self.expire_time = None
        self.yaml_handler = YamlHandler()
        self.token_config = self.yaml_handler.read_yaml("data/test_data.yaml")["token_config"]

    def set_token(self, token, expire_time=None):
        """
        设置token和过期时间
        :param token: token字符串
        :param expire_time: 过期时间戳(秒)
        """
        self.token = token
        if expire_time:
            self.expire_time = expire_time
        else:
            # 如果没有提供过期时间,使用配置的默认过期时间
            self.expire_time = time.time() + self.token_config["expire_time"]

    def is_token_valid(self):
        """
        检查token是否有效
        :return: bool
        """
        if not self.token or not self.expire_time:
            return False
        
        # 检查是否接近过期时间
        remaining_time = self.expire_time - time.time()
        return remaining_time > self.token_config["refresh_before_expire"]

    def get_token(self):
        """
        获取token
        :return: token字符串
        """
        return self.token

@allure.epic("API自动化测试")
class TestAPI:
    def setup_class(self):
        self.http_client = HTTPClient()
        self.assertions = Assertions()
        self.test_data = YamlHandler.read_yaml("data/test_data.yaml")
        self.token_manager = TokenManager()
        self.variable_handler = VariableHandler()
    
    def refresh_token(self):
        """
        刷新token
        """
        with allure.step("刷新token"):
            # 获取登录测试用例数据
            login_case = next(
                case for case in self.test_data["test_cases"] 
                if case["path"] == "/api/login"
            )
            
            # 发送登录请求
            response = self.http_client.request(
                method=login_case["method"],
                path=login_case["path"],
                json=login_case["data"]
            )
            
            # 验证登录响应
            assert response.status_code == 200, "登录失败,无法刷新token"
            
            # 获取新token和过期时间
            response_data = response.json()
            new_token = response_data.get("token")
            expire_time = response_data.get("expire_time")  # 假设接口返回过期时间戳
            
            # 更新token管理器
            self.token_manager.set_token(new_token, expire_time)
            allure.attach("Token已刷新", f"新token: {new_token}", allure.attachment_type.TEXT)
    
    @allure.feature("API测试用例")
    @pytest.mark.parametrize("case", YamlHandler.read_yaml("data/test_data.yaml")["test_cases"])
    def test_api(self, case):
        """API测试用例"""
        allure.dynamic.title(case["case_name"])
        allure.dynamic.description(f"测试接口: {case['path']}")
        
        # 如果是需要token的接口,检查token是否需要刷新
        if "headers" in case and "Authorization" in case["headers"]:
            if not self.token_manager.is_token_valid():
                self.refresh_token()
        
        # 替换请求数据中的变量
        if "headers" in case:
            case["headers"] = self.variable_handler.replace_variables(case["headers"])
        if "data" in case:
            case["data"] = self.variable_handler.replace_variables(case["data"])
        
        with allure.step(f"准备请求数据"):
            headers = case.get("headers", {})
            if "Authorization" in headers:
                headers["Authorization"] = headers["Authorization"].format(
                    token=self.token_manager.get_token()
                )
            allure.attach(
                json.dumps(case.get("data", {}), ensure_ascii=False, indent=2),
                "请求数据",
                allure.attachment_type.JSON
            )
        
        # 发送请求
        with allure.step(f"发送{case['method']}请求到 {case['path']}"):
            request_kwargs = {
                'method': case["method"],
                'path': case["path"],
                'service': case.get("service"),
                'headers': case.get("headers")
            }
            
            # 添加请求数据
            if case.get("data"):
                if case["method"].upper() == "GET":
                    request_kwargs['data'] = case["data"]
                else:
                    request_kwargs['json'] = case["data"]
            
            response = self.http_client.request(**request_kwargs)
            allure.attach(
                json.dumps(response.json(), ensure_ascii=False, indent=2),
                "响应数据",
                allure.attachment_type.JSON
            )
        
        # 保存需要的变量
        if "save_variables" in case and response.status_code == 200:
            with allure.step("保存接口返回变量"):
                self.variable_handler.save_variables(
                    response.json(),
                    case["save_variables"]
                )
        
        # 断言处理
        expected = case["expected"]
        with allure.step("执行断言"):
            if "status_code" in expected:
                with allure.step(f"验证状态码: 期望 {expected['status_code']}"):
                    self.assertions.assert_status_code(response, expected["status_code"])
            
            if "contains_fields" in expected:
                for field in expected["contains_fields"]:
                    with allure.step(f"验证字段存在: {field}"):
                        self.assertions.assert_contains_field(response, field)
            
            if "field_values" in expected:
                for field, value in expected["field_values"].items():
                    with allure.step(f"验证字段值: {field} = {value}"):
                        self.assertions.assert_field_value(response, field, value)
        
        # 如果是登录接口,保存token
        if case["path"] == "/api/login" and response.status_code == 200:
            with allure.step("保存登录token"):
                response_data = response.json()
                self.token_manager.set_token(

http://www.kler.cn/a/550929.html

相关文章:

  • LeetCode 1287.有序数组中出现次数超过25%的元素:遍历
  • 【BUG】LLM|Ubuntu 用 ollama 部署 DeepSeek 但没输出,llama 有输出
  • 【第1章:深度学习概览——1.5 深度学习的核心组件与概念解析之损失函数与优化算法的选择】
  • OneCode 组件分类整理及枚举介绍
  • TensorFlow深度学习实战(8)——卷积神经网络
  • Spring MVC 的核心以及执行流程
  • Python实用技巧:轻松上手自动化数据爬取与存储
  • DeepSeek原理介绍以及对网络安全行业的影响
  • 计时器任务实现(保存视频和图像)
  • 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题
  • 【机器学习】线性回归 多项式线性回归
  • 【MATLAB】解决mod函数、逻辑判断的误差问题
  • 小狐狸ai3.1.2版本源码无授权版本内 含搭建教程+各种上线教程
  • AMD R9-9950X服务器:游戏服务器搭建的终极利器
  • 解锁原型模式:Java 中的高效对象创建之道
  • Linux内存管理:深度解析与探索
  • 春招项目=图床+ k8s 控制台(唬人专用)
  • 如何使用Pytest参数化测试大规模生成单元测试用例
  • 在 Spring 怎么解决循环依赖的问题?
  • 前端模板引擎