Python接口自动化框架:requests封装与logging日志实践

📅 2026/7/5 9:40:07
Python接口自动化框架:requests封装与logging日志实践
1. 项目概述为什么我们需要一个“趁手”的自动化框架做接口测试的朋友尤其是用Python的估计都经历过这个阶段一开始几个测试用例直接写几个requests调用断言一下响应码和关键字段脚本跑起来也挺快。但随着项目迭代接口数量从十几个膨胀到上百个用例数更是成倍增长你会发现之前的脚本越来越难维护。每次改个请求头、换个环境地址都得满世界找散落在各个脚本文件里的硬编码断言失败时日志里只有一句冷冰冰的“AssertionError”得花半天时间去翻请求和响应的原始数据才能定位问题更别提多环境切换、数据驱动、测试报告这些更高级的需求了。这时候一个简单但结构清晰的封装就显得尤为重要。它不是为了炫技而是为了解决实际工程中的痛点统一管理、降低重复、提升可维护性和排错效率。今天要聊的就是基于Python如何搭建一个轻量级但足够实用的接口自动化框架雏形。核心就两点一是对HTTP请求进行合理封装让写用例变得像搭积木一样简单二是深度集成Python标准库的logging模块让测试过程“有迹可循”任何失败都能快速追溯到根因。这个框架不追求大而全但求在中小型项目中能真正用起来提升效率。2. 框架整体设计与核心思路拆解2.1 设计目标与原则在动手写代码之前我们先明确这个封装框架要达成什么目标遵循什么原则。盲目堆砌功能只会制造一个难以维护的“巨无霸”。核心目标有三个用例编写傻瓜化测试工程师的注意力应该放在业务逻辑和测试数据上而不是反复编写requests.post(url, jsondata, headersheaders)这样的样板代码。我们的封装要提供极其简洁的调用方式。日志记录结构化测试执行时需要清晰记录每个步骤发了什么请求收到什么响应断言检查了哪些点。当用例失败时日志应能直接告诉我们“请求是什么响应是什么哪里不匹配”而不是只抛出一个异常。配置管理集中化不同环境开发、测试、生产的域名、通用请求头、超时时间等必须与测试逻辑分离通过配置文件统一管理。遵循的设计原则单一职责每个类或函数只做好一件事。比如一个类只负责发送请求另一个类只负责处理日志格式。约定优于配置提供合理的默认值如默认超时时间、默认日志级别减少不必要的配置项。易于扩展当未来需要支持其他协议如gRPC、WebSocket或更复杂的断言库时现有结构能方便地接入而不是推倒重来。基于这些我们的框架雏形至少包含以下几个核心模块一个Client类HTTP客户端、一个高度定制的日志模块、一个配置文件读取工具。2.2 技术栈选型与考量选型很简单主要依赖Python标准库和几个久经考验的第三方库。HTTP客户端requests为什么选它在Python的HTTP客户端领域requests有着“For Humans”的美誉其API设计直观优雅几乎成为了行业事实标准。相较于标准库的urllib它在易用性上具有压倒性优势。虽然社区也有httpx支持异步和HTTP/2等后起之秀但对于绝大多数同步接口测试场景成熟稳定的requests仍是首选。版本考量建议使用较新的稳定版本如2.x并注意处理可能存在的SSL警告或适配性问题。日志管理logging标准库为什么不用print或第三方库print语句无法分级如DEBUG, INFO, ERROR无法方便地输出到文件且混在代码中难以管理。logging是Python标准库中功能完善、灵活的日志记录模块完全能满足我们的需求。无需引入额外的依赖。核心应用点我们将利用logging的Logger、Formatter和Handler创建同时输出到控制台和文件的日志器并格式化输出请求响应的关键信息。**配置管理configparser标准库或yamlPyYAMLconfigparser适用于.ini格式的配置文件标准库内置无需安装适合简单的键值对配置。YAML配置文件可读性更强支持更复杂的数据结构如列表、字典嵌套。通过PyYAML库可以轻松解析。对于需要分环境、配置复杂参数的场景个人更推荐使用YAML。本项目示例将使用configparser以保持零第三方依赖除requests外。测试组织pytest虽然标题未强调但一个完整的自动化框架最终需要测试运行器。pytest是当前Python测试的事实标准它提供了丰富的夹具fixture、参数化、钩子等机制能很好地与我们封装的Client结合。本文会展示如何将我们的Client封装为pytest的fixture供所有用例使用。3. 核心模块一HTTP请求客户端的封装这是框架的基石。目标是将requests的常用方法GET, POST, PUT, DELETE等封装成一个更易用、功能更统一的类。3.1 Client类的设计与实现我们创建一个api_client.py文件。import requests from typing import Any, Dict, Optional, Union import json class APIClient: 一个简单的HTTP API请求客户端封装类。 def __init__(self, base_url: str , timeout: int 10): 初始化客户端。 Args: base_url: 所有请求的基础URL如https://api.example.com/v1。后续请求的路径会拼接在此之后。 timeout: 默认请求超时时间秒。 self.base_url base_url.rstrip(/) # 移除末尾可能存在的斜杠 self.timeout timeout self.session requests.Session() # 使用Session可以自动保持cookies提升性能 # 可以在这里设置一些默认的session-wide的headers如User-Agent self.session.headers.update({ User-Agent: MyAPITestClient/1.0, Content-Type: application/json # 默认JSON交互可根据实际覆盖 }) def _request(self, method: str, endpoint: str, **kwargs) - requests.Response: 内部请求方法统一处理URL拼接、请求发送和基础日志。 这是所有公共请求方法get, post等的核心。 url f{self.base_url}/{endpoint.lstrip(/)} if self.base_url else endpoint # 处理超时参数如果调用时未指定则使用实例默认值 if timeout not in kwargs: kwargs[timeout] self.timeout # 在真正发送请求前可以在这里记录详细的请求信息DEBUG级别 # 暂时留空后续与logging整合时会填充 # logger.debug(f准备发送请求: {method} {url}) # logger.debug(f请求参数: {kwargs}) try: response self.session.request(methodmethod, urlurl, **kwargs) # 请求发送后记录响应信息INFO级别 # logger.info(f收到响应: {response.status_code} - {url}) except requests.exceptions.Timeout: # 这里可以抛出自定义的超时异常或记录更详细的日志 raise Exception(f请求超时: {method} {url}, 超时设置: {kwargs.get(timeout)}s) except requests.exceptions.RequestException as e: # 处理其他网络请求异常 raise Exception(f网络请求失败: {method} {url}, 错误: {e}) return response # 以下是公共方法提供更友好的API def get(self, endpoint: str, params: Optional[Dict] None, **kwargs) - requests.Response: return self._request(GET, endpoint, paramsparams, **kwargs) def post(self, endpoint: str, data: Optional[Union[Dict, str, bytes]] None, json_data: Optional[Any] None, **kwargs) - requests.Response: 注意requests库本身区分data和json参数。 我们这里暴露两个参数让调用者更清晰。 # 优先使用json_data参数因为它会自动设置Content-Type为application/json并序列化 if json_data is not None: kwargs[json] json_data elif data is not None: kwargs[data] data return self._request(POST, endpoint, **kwargs) def put(self, endpoint: str, json_data: Optional[Any] None, **kwargs) - requests.Response: kwargs[json] json_data return self._request(PUT, endpoint, **kwargs) def delete(self, endpoint: str, **kwargs) - requests.Response: return self._request(DELETE, endpoint, **kwargs) # 可以继续添加patch, head等方法... def set_header(self, key: str, value: str): 动态设置session级别的请求头。 self.session.headers[key] value def clear_header(self, key: str): 动态移除session级别的某个请求头。 self.session.headers.pop(key, None)关键点解析使用requests.Session()Session对象可以跨请求保持某些参数如cookies、headers和TCP连接比直接使用requests.get/post性能更好也更适合测试场景比如登录后保持会话。_request私有方法这是封装的精髓。所有公共方法get,post都委托给这个方法。它统一处理了URL拼接、默认超时设置并集中了异常处理逻辑。未来我们整合日志时也只需要修改这一个地方。灵活的参数传递**kwargs允许调用者传递任何requests.request支持的参数如headers,files,auth等保持了灵活性。同时我们为post方法提供了更明确的json_data参数方便发送JSON数据。清晰的API对外暴露的get、post等方法签名清晰符合直觉让编写测试用例的同事几乎不需要查阅文档。3.2 与配置文件结合硬编码base_url是不可接受的。我们需要从配置文件读取。创建一个config.ini文件[API] base_url https://jsonplaceholder.typicode.com timeout 15 [LOG] level INFO file_path logs/api_test.log然后我们修改APIClient的初始化或者创建一个工厂函数/从单独配置模块读取# config_loader.py import os import configparser from pathlib import Path def load_config(config_file: str config.ini) - configparser.ConfigParser: 加载配置文件。 config configparser.ConfigParser() # 可以在这里设置一些默认值 config.read_dict({ API: {base_url: , timeout: 10}, LOG: {level: INFO, file_path: logs/test.log} }) # 读取外部文件覆盖默认值 file_path Path(__file__).parent / config_file if file_path.exists(): config.read(file_path) else: print(f警告: 配置文件 {config_file} 不存在使用默认配置。) return config # 获取配置 CONFIG load_config() API_BASE_URL CONFIG.get(API, base_url) API_TIMEOUT CONFIG.getint(API, timeout)在创建APIClient实例时from config_loader import API_BASE_URL, API_TIMEOUT client APIClient(base_urlAPI_BASE_URL, timeoutAPI_TIMEOUT)4. 核心模块二结构化日志的深度集成日志是测试的“眼睛”。一个好的日志系统能在出问题时帮你节省大量排查时间。我们将把logging模块深度集成到APIClient中。4.1 定制日志格式与输出我们不希望日志只是简单的文本行而是希望每条日志都包含时间、级别、模块名和具体消息。对于请求和响应我们希望以结构化的方式如JSON记录关键信息便于后续搜索和分析。创建一个logger_setup.py文件import logging import sys from config_loader import CONFIG # 导入之前的配置 def setup_logger(name: str api_test) - logging.Logger: 设置并返回一个配置好的logger实例。 logger logging.getLogger(name) # 避免重复添加handler在多次调用时 if logger.handlers: return logger log_level getattr(logging, CONFIG.get(LOG, level).upper(), logging.INFO) logger.setLevel(log_level) # 1. 控制台处理器 (StreamHandler) console_handler logging.StreamHandler(sys.stdout) console_format logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(message)s, datefmt%Y-%m-%d %H:%M:%S ) console_handler.setFormatter(console_format) logger.addHandler(console_handler) # 2. 文件处理器 (FileHandler) log_file_path CONFIG.get(LOG, file_path) # 确保日志目录存在 from pathlib import Path Path(log_file_path).parent.mkdir(parentsTrue, exist_okTrue) file_handler logging.FileHandler(log_file_path, encodingutf-8) # 文件日志可以使用更详细的格式或者JSON格式以便于日志分析系统处理 file_format logging.Formatter( %(asctime)s | %(name)s | %(levelname)s | %(filename)s:%(lineno)d | %(message)s ) file_handler.setFormatter(file_format) logger.addHandler(file_handler) # 设置requests库自身的日志级别为WARNING避免它输出太多调试信息干扰我们 logging.getLogger(urllib3).setLevel(logging.WARNING) return logger # 创建一个全局可用的logger实例 logger setup_logger()4.2 将Logger注入APIClient现在我们需要改造APIClient._request方法在关键节点记录日志。# 在api_client.py顶部导入我们创建的logger from logger_setup import logger class APIClient: # ... __init__ 等其他部分不变 ... def _request(self, method: str, endpoint: str, **kwargs) - requests.Response: url f{self.base_url}/{endpoint.lstrip(/)} if self.base_url else endpoint if timeout not in kwargs: kwargs[timeout] self.timeout # 关键记录请求详情 # 使用DEBUG级别记录最详细的信息如完整的请求体、头注意过滤敏感信息如Authorization logger.debug(f发送请求 - 方法: {method}, URL: {url}) # 小心记录敏感信息生产环境建议过滤或使用占位符 safe_headers {k: v for k, v in self.session.headers.items()} if Authorization in safe_headers: safe_headers[Authorization] *** logger.debug(f请求头: {safe_headers}) if json in kwargs: logger.debug(f请求体(JSON): {kwargs[json]}) elif data in kwargs: logger.debug(f请求体(DATA): {kwargs[data]}) try: response self.session.request(methodmethod, urlurl, **kwargs) # 关键记录响应详情 # 使用INFO级别记录请求-响应对这是最常用的排查信息 log_msg ( f请求完成 - 方法: {method}, URL: {url}, f状态码: {response.status_code}, f耗时: {response.elapsed.total_seconds():.3f}s ) logger.info(log_msg) # 使用DEBUG级别记录详细的响应内容可能很大 logger.debug(f响应头: {dict(response.headers)}) # 尝试解析JSON响应体如果是文本则直接记录 try: resp_body response.json() logger.debug(f响应体(JSON): {resp_body}) except json.JSONDecodeError: logger.debug(f响应体(Text): {response.text[:500]}...) # 只记录前500字符 except requests.exceptions.Timeout: logger.error(f请求超时 - 方法: {method}, URL: {url}, 超时设置: {kwargs.get(timeout)}s) raise Exception(f请求超时: {method} {url}) except requests.exceptions.RequestException as e: logger.error(f网络请求失败 - 方法: {method}, URL: {url}, 错误: {e}) raise Exception(f网络请求失败: {method} {url}, 错误: {e}) return response日志策略解析DEBUG级别用于记录最详细的技术细节如完整的请求/响应头、请求/响应体。在本地调试或排查复杂问题时开启。切记此级别可能包含敏感信息如Token、密码切勿在CI/CD流水线或生产日志中开启此级别并明文输出。INFO级别记录核心事件如“请求完成”包含方法、URL、状态码、耗时。这是日常查看测试执行情况的主要信息来源。ERROR级别记录异常和失败如超时、网络错误。敏感信息处理示例中简单过滤了Authorization头。在实际项目中你需要根据实际情况定义一个更全面的敏感信息过滤列表如password,token,secret等字段在记录前对数据进行清洗。5. 整合与实战编写第一个测试用例现在我们将封装好的Client和Logger与pytest结合起来编写一个完整的测试用例。5.1 创建pytest fixture在conftest.py文件中pytest会自动发现此文件我们定义一个clientfixture这样所有测试用例都可以方便地使用同一个配置好的客户端实例。# conftest.py import pytest from api_client import APIClient from config_loader import API_BASE_URL, API_TIMEOUT pytest.fixture(scopesession) # session级别所有测试用例共享一个client实例 def api_client(): 提供一个配置好的APIClient实例。 client APIClient(base_urlAPI_BASE_URL, timeoutAPI_TIMEOUT) # 可以在这里进行一些全局的初始化比如登录获取token并设置到header # token get_auth_token(client) # client.set_header(Authorization, fBearer {token}) yield client # 测试结束后可以在这里进行清理如关闭sessionrequests.Session会自动处理 client.session.close()5.2 编写测试用例创建一个测试文件test_posts_api.py。# test_posts_api.py import pytest class TestPostsAPI: 针对JSONPlaceholder的/posts接口的测试。 def test_get_all_posts(self, api_client): 测试获取所有帖子。 # 调用封装的get方法非常简洁 response api_client.get(posts) # 断言状态码 assert response.status_code 200 # 断言响应体是列表 resp_json response.json() assert isinstance(resp_json, list) # 断言列表不为空根据实际业务 assert len(resp_json) 0 # 可以断言第一个帖子包含必要的字段 first_post resp_json[0] assert id in first_post assert title in first_post assert body in first_post assert userId in first_post def test_get_single_post(self, api_client): 测试获取指定ID的帖子。 post_id 1 response api_client.get(fposts/{post_id}) assert response.status_code 200 post response.json() assert post[id] post_id # 更复杂的断言可以使用jsonpath或自定义函数 def test_create_post(self, api_client): 测试创建新帖子。 new_post_data { title: My New Post, body: This is the body of my new post., userId: 1 } # 使用封装的post方法通过json_data参数传递字典会自动序列化为JSON response api_client.post(posts, json_datanew_post_data) assert response.status_code 201 # 创建成功通常是201 created_post response.json() # 断言返回的数据包含了我们发送的数据 assert created_post[title] new_post_data[title] assert created_post[body] new_post_data[body] assert created_post[userId] new_post_data[userId] # 通常会有一个新的id assert id in created_post assert isinstance(created_post[id], int) # 可以继续添加更新、删除等测试用例...5.3 运行测试并查看日志在项目根目录下运行pytest test_posts_api.py -v你会看到pytest输出的测试结果。同时在控制台和logs/api_test.log文件中你会看到结构化的日志输出控制台输出示例2023-10-27 10:30:15 - api_test - INFO - 请求完成 - 方法: GET, URL: https://jsonplaceholder.typicode.com/posts, 状态码: 200, 耗时: 0.452s 2023-10-27 10:30:16 - api_test - INFO - 请求完成 - 方法: GET, URL: https://jsonplaceholder.typicode.com/posts/1, 状态码: 200, 耗时: 0.123s 2023-10-27 10:30:17 - api_test - INFO - 请求完成 - 方法: POST, URL: https://jsonplaceholder.typicode.com/posts, 状态码: 201, 耗时: 0.234s日志文件输出示例更详细2023-10-27 10:30:17 | api_test | INFO | test_posts_api.py:25 | 请求完成 - 方法: POST, URL: https://jsonplaceholder.typicode.com/posts, 状态码: 201, 耗时: 0.234s如果某个断言失败pytest会报告错误结合日志文件中的请求和响应详情如果你设置了DEBUG级别你可以迅速定位是请求参数不对还是响应结果不符合预期。6. 高级封装与实用技巧基础的封装已经能解决80%的问题。下面分享一些让框架更“好用”的技巧。6.1 响应对象的再封装requests.Response对象功能强大但有时我们希望在它基础上增加一些便捷方法。例如一个常见的需求是断言响应状态码在2xx范围内并自动解析JSON如果失败则抛出包含详细信息的断言错误。我们可以创建一个CustomResponse类来包装它# response_wrapper.py import json from requests.models import Response class CustomResponse: 对requests.Response的包装提供更多便捷方法。 def __init__(self, raw_response: Response): self._raw raw_response self.status_code raw_response.status_code self.headers raw_response.headers self.elapsed raw_response.elapsed property def text(self): return self._raw.text def json(self): 解析JSON如果解析失败记录更友好的错误信息。 try: return self._raw.json() except json.JSONDecodeError as e: # 记录原始文本片段帮助调试 snippet self._raw.text[:200] raise ValueError(f响应不是有效的JSON。错误: {e}。响应开头: {snippet}) def raise_for_status_with_detail(self): 类似于requests的raise_for_status但包含更多上下文信息。 if 400 self.status_code 600: # 尝试获取错误信息 error_detail try: error_body self._raw.json() error_detail f 错误详情: {error_body} except: error_detail f 响应文本: {self._raw.text[:500]} raise Exception( fHTTP错误 {self.status_code} for URL: {self._raw.url}.{error_detail} ) return self def assert_status_code(self, expected_code: int): 断言状态码等于期望值。 assert self.status_code expected_code, \ f状态码断言失败。期望: {expected_code}, 实际: {self.status_code}。URL: {self._raw.url} return self # 支持链式调用 def assert_json_schema(self, schema: dict): 使用jsonschema库验证响应JSON结构需要安装jsonschema。 # 这里只是一个示意实际实现需要引入jsonschema库 # from jsonschema import validate, ValidationError # try: # validate(instanceself.json(), schemaschema) # except ValidationError as e: # raise AssertionError(fJSON Schema验证失败: {e.message}) pass然后修改APIClient._request方法最后返回CustomResponse对象# 在_request方法最后 return CustomResponse(response)这样在测试用例中你可以进行链式断言def test_chain_assertion(self, api_client): response api_client.get(posts/1) (response.assert_status_code(200) .raise_for_status_with_detail()) post response.json() assert post[id] 16.2 使用pytest夹具管理测试数据测试数据尤其是用于创建、更新的数据最好与测试逻辑分离。可以使用pytest的pytest.fixture来提供。# conftest.py 或 测试文件内 import pytest pytest.fixture def new_post_data(): 提供创建帖子所需的测试数据。 return { title: Test Post Title, body: This is a test post body content for automation., userId: 999 } pytest.fixture def update_post_data(): 提供更新帖子所需的测试数据。 return { title: Updated Title, body: Updated body content. }在测试用例中使用def test_create_with_fixture(self, api_client, new_post_data): response api_client.post(posts, json_datanew_post_data) # ... 断言 ...6.3 参数化测试对于需要测试多组输入输出数据的场景pytest的pytest.mark.parametrize是利器。import pytest pytest.mark.parametrize(post_id, expected_status, [ (1, 200), (0, 404), # 假设ID为0不存在 (99999, 404), # 假设ID过大不存在 (abc, 400), # 无效ID类型 ]) def test_get_post_with_different_ids(self, api_client, post_id, expected_status): 测试获取不同ID的帖子验证边界和异常情况。 # 注意对于非数字IDURL拼接可能需要处理这里假设接口能处理 response api_client.get(fposts/{post_id}) response.assert_status_code(expected_status)7. 常见问题与排查技巧实录在实际使用中你肯定会遇到各种问题。这里记录几个典型场景和解决思路。7.1 日志文件不生成或为空问题运行测试后logs/api_test.log文件没有创建或者创建了但里面是空的。排查检查目录权限确保运行脚本的用户对logs目录有写权限。我们的代码中Path.mkdir(parentsTrue, exist_okTrue)会尝试创建目录但如果父目录权限不足也会失败。可以在代码创建目录后加一句print(Path(log_file_path).parent.is_dir())来检查。检查logger级别如果控制台有日志但文件没有很可能是文件处理器的级别设置得比日志记录级别高。例如logger是DEBUG级别但文件处理器设置了setLevel(logging.ERROR)那么DEBUG和INFO级别的日志就不会写入文件。确保文件处理器的级别足够低如logging.DEBUG。检查logger命名冲突如果你在多个模块中logging.getLogger(api_test)并且配置了不同的处理器可能会产生冲突。确保setup_logger函数中的if logger.handlers:判断逻辑正确或者使用不同的logger名称。7.2 请求超时或响应慢问题测试用例经常因超时而失败或者响应时间很长。排查与解决调整默认超时在config.ini中适当增加[API]部分的timeout值。但这不是根本解决办法。检查网络和环境确认测试环境base_url的网络是通畅的服务本身是健康的。可以先用curl或Postman手动测试一下。检查请求数据大小如果POST或PUT的请求体非常大可能导致传输慢。检查测试数据是否合理。使用Session我们已经使用了requests.Session()它保持了TCP连接复用对连续请求同一个主机的场景有性能提升。确保你的clientfixture是session或module级别的而不是function级别每次测试都新建连接。记录耗时我们的日志已经记录了response.elapsed。分析哪些接口耗时异常针对性优化或联系开发。7.3 断言失败时日志信息不足问题测试失败时pytest只输出简单的断言错误看不到当时的请求和响应详情。解决确保DEBUG日志已开启在排查问题时临时修改config.ini中的level DEBUG重新运行测试。失败时查看日志文件中的DEBUG信息里面包含了请求和响应的完整详情。使用pytest的-s和-v参数运行pytest -v -s可以禁止输出捕获让测试过程中的print语句和部分日志实时输出到控制台。定制断言错误信息像我们上面封装的assert_status_code方法在断言失败时主动输出URL和状态码对比比单纯的assert response.status_code 200信息量更大。7.4 如何处理需要登录的接口场景大部分业务接口都需要认证如JWT Token。方案在fixture中完成登录在conftest.py的api_clientfixture中在yield client之前调用登录接口获取token并设置到client.session.headers[Authorization]中。使用独立的认证fixture创建一个auth_tokenfixture返回token。然后在每个需要认证的测试用例中传入这个fixture并在请求前手动设置header。这种方式更灵活适合不同角色不同权限的测试。注意token过期如果测试套件运行时间很长token可能过期。可以考虑在client中增加一个token刷新的逻辑或者在请求失败返回401时自动重新登录并重试请求需谨慎实现避免无限循环。7.5 如何组织大量测试用例和文件建议按业务模块分目录例如tests/user/,tests/order/,tests/product/。使用conftest.py的层级每个子目录可以有自己的conftest.py定义该模块特有的fixture。pytest会自动合并所有conftest.py中的fixture。给测试类和方法起好名字类名TestXXXAPI方法名test_场景_预期结果例如test_create_user_with_valid_data_success。使用标记mark用pytest.mark.smoke标记冒烟测试用例用pytest.mark.parametrize标记数据驱动测试用pytest.mark.skip或pytest.mark.skipif跳过某些测试。然后可以用pytest -m smoke只运行冒烟测试。这个简单的封装框架从几十行代码开始已经逐渐具备了支撑一个真实项目接口自动化测试的雏形。它可能没有HttpRunner或Pytest-Requests那样功能全面但它的优势在于完全贴合你的项目需求每一行代码你都了如指掌扩展和修改起来也极其方便。下次当你面对一堆零散的requests脚本时不妨花上半天时间把它们重构到这样一个框架里后续的维护效率会让你觉得这半天时间花得物超所值。