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

打印 上一主题 下一主题

主题 701|帖子 701|积分 2103

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.项目背景和目的

   这是一个基于Python的接口主动化测试框架,主要目的是:
  - 提供一个稳定、可维护的接口测试办理方案
  - 实现测试用例与测试数据的解耦
  - 提供具体的测试陈诉和日志记载
  - 支持多环境配置和机动的用例管理
  2.技术栈选型

  框架接纳了以下核心技术:
  pytest:作为测试实验引擎,提供了强大的用例管理和参数化能力
  requests:处理HTTP请求,支持各种接口调用场景
  allure:天生雅观的测试陈诉,支持具体的测试步骤记载
  yaml:管理配置文件和测试数据,提供更好的可读性和维护性
  logging:实现完备的日志记载体系
  
  3.项目分层架构计划

  让我具体介绍这个项目的分层架构计划:
  
      
  • 测试用例层 (testcases/) - test_api.py:测试用例实现
       - 用例编写和管理
       - 测试流程控制
       - 测试数据参数化
       - 断言结果验证

  1. import pytest
  2. import allure
  3. import json
  4. import time
  5. from datetime import datetime
  6. from common.http_client import HTTPClient
  7. from common.assertions import Assertions
  8. from common.yaml_handler import YamlHandler
  9. from common.variable_handler import VariableHandler
  10. class TokenManager:
  11.     def __init__(self):
  12.         self.token = None
  13.         self.expire_time = None
  14.         self.yaml_handler = YamlHandler()
  15.         self.token_config = self.yaml_handler.read_yaml("data/test_data.yaml")["token_config"]
  16.     def set_token(self, token, expire_time=None):
  17.         """
  18.         设置token和过期时间
  19.         :param token: token字符串
  20.         :param expire_time: 过期时间戳(秒)
  21.         """
  22.         self.token = token
  23.         if expire_time:
  24.             self.expire_time = expire_time
  25.         else:
  26.             # 如果没有提供过期时间,使用配置的默认过期时间
  27.             self.expire_time = time.time() + self.token_config["expire_time"]
  28.     def is_token_valid(self):
  29.         """
  30.         检查token是否有效
  31.         :return: bool
  32.         """
  33.         if not self.token or not self.expire_time:
  34.             return False
  35.         
  36.         # 检查是否接近过期时间
  37.         remaining_time = self.expire_time - time.time()
  38.         return remaining_time > self.token_config["refresh_before_expire"]
  39.     def get_token(self):
  40.         """
  41.         获取token
  42.         :return: token字符串
  43.         """
  44.         return self.token
  45. @allure.epic("API自动化测试")
  46. class TestAPI:
  47.     def setup_class(self):
  48.         self.http_client = HTTPClient()
  49.         self.assertions = Assertions()
  50.         self.test_data = YamlHandler.read_yaml("data/test_data.yaml")
  51.         self.token_manager = TokenManager()
  52.         self.variable_handler = VariableHandler()
  53.    
  54.     def refresh_token(self):
  55.         """
  56.         刷新token
  57.         """
  58.         with allure.step("刷新token"):
  59.             # 获取登录测试用例数据
  60.             login_case = next(
  61.                 case for case in self.test_data["test_cases"]
  62.                 if case["path"] == "/api/login"
  63.             )
  64.             
  65.             # 发送登录请求
  66.             response = self.http_client.request(
  67.                 method=login_case["method"],
  68.                 path=login_case["path"],
  69.                 json=login_case["data"]
  70.             )
  71.             
  72.             # 验证登录响应
  73.             assert response.status_code == 200, "登录失败,无法刷新token"
  74.             
  75.             # 获取新token和过期时间
  76.             response_data = response.json()
  77.             new_token = response_data.get("token")
  78.             expire_time = response_data.get("expire_time")  # 假设接口返回过期时间戳
  79.             
  80.             # 更新token管理器
  81.             self.token_manager.set_token(new_token, expire_time)
  82.             allure.attach("Token已刷新", f"新token: {new_token}", allure.attachment_type.TEXT)
  83.    
  84.     @allure.feature("API测试用例")
  85.     @pytest.mark.parametrize("case", YamlHandler.read_yaml("data/test_data.yaml")["test_cases"])
  86.     def test_api(self, case):
  87.         """API测试用例"""
  88.         allure.dynamic.title(case["case_name"])
  89.         allure.dynamic.description(f"测试接口: {case['path']}")
  90.         
  91.         # 如果是需要token的接口,检查token是否需要刷新
  92.         if "headers" in case and "Authorization" in case["headers"]:
  93.             if not self.token_manager.is_token_valid():
  94.                 self.refresh_token()
  95.         
  96.         # 替换请求数据中的变量
  97.         if "headers" in case:
  98.             case["headers"] = self.variable_handler.replace_variables(case["headers"])
  99.         if "data" in case:
  100.             case["data"] = self.variable_handler.replace_variables(case["data"])
  101.         
  102.         with allure.step(f"准备请求数据"):
  103.             headers = case.get("headers", {})
  104.             if "Authorization" in headers:
  105.                 headers["Authorization"] = headers["Authorization"].format(
  106.                     token=self.token_manager.get_token()
  107.                 )
  108.             allure.attach(
  109.                 json.dumps(case.get("data", {}), ensure_ascii=False, indent=2),
  110.                 "请求数据",
  111.                 allure.attachment_type.JSON
  112.             )
  113.         
  114.         # 发送请求
  115.         with allure.step(f"发送{case['method']}请求到 {case['path']}"):
  116.             request_kwargs = {
  117.                 'method': case["method"],
  118.                 'path': case["path"],
  119.                 'service': case.get("service"),
  120.                 'headers': case.get("headers")
  121.             }
  122.             
  123.             # 添加请求数据
  124.             if case.get("data"):
  125.                 if case["method"].upper() == "GET":
  126.                     request_kwargs['data'] = case["data"]
  127.                 else:
  128.                     request_kwargs['json'] = case["data"]
  129.             
  130.             response = self.http_client.request(**request_kwargs)
  131.             allure.attach(
  132.                 json.dumps(response.json(), ensure_ascii=False, indent=2),
  133.                 "响应数据",
  134.                 allure.attachment_type.JSON
  135.             )
  136.         
  137.         # 保存需要的变量
  138.         if "save_variables" in case and response.status_code == 200:
  139.             with allure.step("保存接口返回变量"):
  140.                 self.variable_handler.save_variables(
  141.                     response.json(),
  142.                     case["save_variables"]
  143.                 )
  144.         
  145.         # 断言处理
  146.         expected = case["expected"]
  147.         with allure.step("执行断言"):
  148.             if "status_code" in expected:
  149.                 with allure.step(f"验证状态码: 期望 {expected['status_code']}"):
  150.                     self.assertions.assert_status_code(response, expected["status_code"])
  151.             
  152.             if "contains_fields" in expected:
  153.                 for field in expected["contains_fields"]:
  154.                     with allure.step(f"验证字段存在: {field}"):
  155.                         self.assertions.assert_contains_field(response, field)
  156.             
  157.             if "field_values" in expected:
  158.                 for field, value in expected["field_values"].items():
  159.                     with allure.step(f"验证字段值: {field} = {value}"):
  160.                         self.assertions.assert_field_value(response, field, value)
  161.         
  162.         # 如果是登录接口,保存token
  163.         if case["path"] == "/api/login" and response.status_code == 200:
  164.             with allure.step("保存登录token"):
  165.                 response_data = response.json()
  166.                 self.token_manager.set_token(
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦应逍遥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表