Python+Requests+Unittest+Excel构建轻量级接口自动化测试框架实战
1. 项目概述与核心价值如果你是一名测试工程师或者正在向这个方向转型那么“接口自动化测试”这个词你一定不陌生。但很多时候我们会被各种高大上的框架、复杂的持续集成概念吓退感觉无从下手。今天我想和你分享一个我用了很多年并且带过不少新人上手的实战方案用 Python requests unittest Excel 来搭建一个轻量级、易上手、且完全够用的接口自动化测试框架。这个组合听起来可能不够“时髦”没有用上 Pytest、Allure 或者 Jenkins 这些更现代的工具链。但它的优势恰恰在于其极致的简单和透明。Requests让你用最直观的方式发送 HTTP 请求Unittest是 Python 自带的单元测试框架意味着零额外依赖结构清晰Excel则充当了可视化的数据驱动引擎测试用例、预期结果、甚至测试数据都放在里面业务和产品同学也能看懂、能参与维护。这个框架的核心价值不是追求技术的炫酷而是切实地解决“如何快速、可靠、可维护地执行大量接口回归测试”这个实际问题。它特别适合中小型项目、测试团队转型自动化的初期或者作为个人提升测试技能、理解自动化测试本质的练手项目。接下来我会把这个框架从设计思路到每一行代码再到实际踩过的坑毫无保留地拆解给你。2. 框架整体设计与核心思路拆解在动手写代码之前我们先得想清楚这个框架要干什么以及为什么是这“四件套”。一个合格的自动化测试框架至少要满足几个基本诉求能方便地组织和管理测试用例、能灵活地准备测试数据和验证点、能清晰地输出测试报告、并且易于维护和扩展。我们这个“复古”组合就是围绕这些诉求展开的。2.1 技术选型背后的逻辑为什么是它们Python是基础这没什么好说的。在测试领域Python 的语法简洁、库生态丰富几乎是自动化测试的首选语言。它降低了编码门槛让测试人员能更专注于测试逻辑本身。Requests库是处理 HTTP 请求的“瑞士军刀”。相比于 Python 内置的 urllibRequests 的 API 设计更加人性化。发送一个 GET 请求只需要requests.get(url)返回的响应对象包含了状态码、响应头、响应体自动解析为 JSON 或文本访问起来非常直观。对于接口测试这种以 HTTP 协议为核心的活动Requests 极大地提升了编写用例的效率。Unittest是 Python 标准库中的单元测试框架。选择它而不是更流行的 Pytest主要基于两点考虑。第一零依赖。这意味着在任何有 Python 环境的地方都能直接运行部署成本极低特别适合在受限的 CI 环境中快速拉起测试。第二结构强制清晰。Unittest 要求你使用TestCase类来组织测试方法使用setUp和tearDown来处理前置和后置操作。这种略显“刻板”的结构对于初学自动化的人来说反而是一种很好的规范能帮助你建立起“测试套件-测试用例-测试步骤”的层次化思维。Excel在这里扮演的是“数据驱动”和“用例管理”的双重角色。将测试数据如请求参数、预期结果如状态码、关键字段值从代码中剥离出来存放在 Excel 文件中。这样做的好处显而易见业务与代码分离测试人员或产品经理即使不懂代码也可以在 Excel 中设计、修改和评审测试用例。易于维护当接口参数发生变化时通常只需要修改 Excel 中的数据文件而无需深入代码逻辑。灵活性高可以轻松地通过复制行来生成多条测试数据实现参数化测试。注意很多人会质疑 Excel 的维护性比如合并单元格、公式错误等问题。在实际操作中我们会有严格的模板规范并配合使用openpyxl库来读写只操作特定的数据区域避免这些问题。对于更复杂的场景后期可以平滑迁移到 YAML、JSON 或数据库中。2.2 框架目录结构设计一个清晰的目录结构是框架可维护性的基石。在项目根目录下我通常会这样组织api_auto_framework/ ├── common/ # 公共模块 │ ├── __init__.py │ ├── logger.py # 日志模块 │ ├── config.py # 配置文件读取如数据库、环境URL │ └── request_handler.py # 对requests的二次封装 ├── data/ # 测试数据文件 │ └── test_cases.xlsx # Excel格式的测试用例文件 ├── testcases/ # 测试用例脚本 │ ├── __init__.py │ ├── test_login.py # 登录模块测试类 │ └── test_order.py # 订单模块测试类 ├── reports/ # 测试报告输出目录 │ └── (HTML报告、日志文件会生成在这里) ├── utils/ # 工具函数 │ ├── __init__.py │ ├── excel_handler.py # Excel读写工具类 │ └── assert_utils.py # 自定义断言工具 └── run_tests.py # 主运行入口这个结构的核心思想是“分离关注点”。common放通用的、与具体业务无关的代码testcases里是具体的测试逻辑data和utils为测试用例提供数据和工具支持。run_tests.py作为总开关负责发现、加载并运行所有测试最后生成报告。3. 核心模块详解与工具类封装框架的骨架搭好了接下来我们要给这个骨架注入灵魂——也就是各个核心功能模块的实现。这部分是代码复用的关键写好了以后编写具体的测试用例就会像搭积木一样简单。3.1 请求处理核心封装一个健壮的RequestHandler直接使用requests.get()或post()在测试脚本里写会有大量重复代码比如异常处理、日志记录、通用头信息设置等。我们需要一个更高级的封装。在common/request_handler.py中我们创建一个类import requests import json from common.logger import get_logger logger get_logger(__name__) class RequestHandler: def __init__(self): self.session requests.Session() # 使用Session保持会话如登录态 # 可以在这里设置一些默认请求头如 Content-Type self.default_headers { Content-Type: application/json; charsetUTF-8 } def send_request(self, method, url, paramsNone, dataNone, json_dataNone, headersNone, **kwargs): 发送HTTP请求的核心方法 :param method: 请求方法GET, POST, PUT, DELETE :param url: 请求URL :param params: URL查询参数字典 :param data: 表单格式的请求体 :param json_data: JSON格式的请求体 :param headers: 请求头 :param kwargs: 其他传递给requests.request的参数 :return: 响应对象如果发生异常则返回None # 合并默认头和自定义头 if headers: headers {**self.default_headers, **headers} else: headers self.default_headers.copy() # 记录请求日志脱敏处理后的 log_msg f发送请求: {method} {url} if params: log_msg f\nParams: {params} # 注意实际日志中应对敏感信息如密码进行脱敏这里为示例简化 if json_data: log_msg f\nJson Body: {json_data} elif data: log_msg f\nForm Data: {data} logger.info(log_msg) try: response self.session.request( methodmethod.upper(), urlurl, paramsparams, datadata, jsonjson_data, headersheaders, **kwargs ) # 记录响应日志 logger.info(f收到响应: 状态码{response.status_code}, 耗时{response.elapsed.total_seconds():.2f}s) # 尝试记录响应体前500字符避免过大日志 try: resp_body response.json() logger.debug(f响应体(JSON): {json.dumps(resp_body, ensure_asciiFalse)[:500]}) except: logger.debug(f响应体(Text): {response.text[:500]}) return response except requests.exceptions.ConnectionError as e: logger.error(f网络连接错误: {e}) return None except requests.exceptions.Timeout as e: logger.error(f请求超时: {e}) return None except Exception as e: logger.error(f请求发送未知异常: {e}) return None # 提供便捷方法 def get(self, url, paramsNone, **kwargs): return self.send_request(GET, url, paramsparams, **kwargs) def post(self, url, json_dataNone, dataNone, **kwargs): return self.send_request(POST, url, json_datajson_data, datadata, **kwargs) # 可以继续封装 put, delete 等方法...封装要点解析使用 Sessionrequests.Session()可以自动处理 Cookies在一次会话中保持登录状态这对于需要鉴权的接口测试至关重要。统一的日志输出每个请求和响应都被详细记录包括URL、方法、参数、状态码和耗时。这在调试和排查问题时是无价之宝。注意生产代码中一定要对密码、token等敏感信息进行脱敏处理如用***替换。集中异常处理将网络超时、连接错误等异常在底层捕获并记录避免每个测试用例都写一遍try...except。方法返回None或响应对象由上层调用者决定如何处理比如断言失败。提供便捷方法像get(),post()这样的方法让调用更简洁。3.2 数据驱动引擎打造灵活的ExcelHandler测试数据存在 Excel 里我们需要一个工具来读取它。这里使用openpyxl库因为它能很好地处理.xlsx格式功能强大。在utils/excel_handler.py中import openpyxl from openpyxl import load_workbook import os class ExcelHandler: 用于读取Excel测试数据的工具类 def __init__(self, file_path): 初始化加载Excel文件 :param file_path: Excel文件的绝对路径 if not os.path.exists(file_path): raise FileNotFoundError(f测试数据文件不存在: {file_path}) self.file_path file_path self.wb load_workbook(file_path, data_onlyTrue) # data_onlyTrue 只读值不读公式 self.ws None def get_sheet_by_name(self, sheet_name): 切换到指定名称的工作表 if sheet_name in self.wb.sheetnames: self.ws self.wb[sheet_name] return self else: raise ValueError(f工作表 {sheet_name} 不存在于文件 {self.file_path} 中) def read_data(self, min_row2, min_col1, max_colNone): 读取当前工作表的数据默认从第2行开始第1行是表头 :param min_row: 起始行 :param min_col: 起始列 :param max_col: 最大列数如果为None则自动判断 :return: 列表每个元素是一行数据字典形式键为表头 if not self.ws: raise RuntimeError(请先使用 get_sheet_by_name 选择工作表) # 确定最大列 if max_col is None: max_col self.ws.max_column # 读取表头 headers [] for col in range(min_col, max_col 1): cell_value self.ws.cell(row1, columncol).value headers.append(cell_value if cell_value is not None else fCol{col}) # 读取数据行 test_data [] for row in range(min_row, self.ws.max_row 1): row_data {} is_empty_row True # 标记是否为空行 for col, header in enumerate(headers, startmin_col): cell_value self.ws.cell(rowrow, columncol).value row_data[header] cell_value if cell_value is not None: is_empty_row False if not is_empty_row: # 只添加非空行 test_data.append(row_data) return test_data def write_result(self, row, col, value): 将测试结果写回Excel的指定单元格 :param row: 行号 (从1开始) :param col: 列号 (从1开始) 或 列名 (如A) :param value: 要写入的值 if isinstance(col, str): # 如果col是A这样的列名转换为数字 col openpyxl.utils.column_index_from_string(col) self.ws.cell(rowrow, columncol, valuevalue) def save(self, new_file_pathNone): 保存Excel文件可另存为新文件 save_path new_file_path or self.file_path self.wb.save(save_path) print(f文件已保存: {save_path}) def close(self): 关闭工作簿释放资源 self.wb.close()设计细节与避坑指南表头驱动read_data方法的核心逻辑是“第一行是表头”。它将每一行数据转换成一个字典键是表头单元格的值值是对应的数据。这样在测试用例中就可以用case[‘url’]、case[‘expected_code’]这样的方式来访问数据非常直观。处理空行和空值代码中通过is_empty_row标志跳过了完全为空的行避免读取到无意义的空数据。对于单元格的None值会原样保留在字典中在构造请求时需要注意比如json.dumps会忽略None但data参数可能不会。data_onlyTrue加载工作簿时使用这个参数非常重要。它确保读取的是单元格最终显示的值而不是公式本身。如果你的 Excel 里用了公式计算测试数据这个参数能让你拿到计算结果。结果回写write_result方法允许我们将测试的实际结果如Pass/Fail或关键信息写回 Excel 对应的单元格。这能生成一份带结果的测试数据文档便于后续分析和归档。注意频繁的保存操作save会影响性能通常是在所有测试运行完毕后一次性保存。3.3 断言增强让验证更智能的AssertUtilsUnittest 自带的断言方法如assertEqual,assertTrue有时在接口测试中不够用。我们需要一个能处理 JSON 响应、支持复杂校验的工具类。在utils/assert_utils.py中import jsonpath from common.logger import get_logger logger get_logger(__name__) class AssertUtils: staticmethod def assert_status_code(response, expected_code): 断言响应状态码 actual_code response.status_code if response else None assert actual_code expected_code, f状态码断言失败预期: {expected_code}, 实际: {actual_code} logger.info(f状态码断言通过: {expected_code}) staticmethod def assert_json_value_by_path(response, json_path, expected_value): 使用jsonpath断言响应JSON中某个路径的值 :param response: requests.Response 对象 :param json_path: jsonpath表达式如 $.data.token :param expected_value: 期望的值 if not response: raise AssertionError(响应对象为None无法进行JSON断言) try: resp_json response.json() except ValueError: raise AssertionError(f响应体不是有效的JSON格式: {response.text[:200]}) actual_values jsonpath.jsonpath(resp_json, json_path) if actual_values is False: # jsonpath 未找到时返回False raise AssertionError(f在响应JSON中未找到路径: {json_path}) # jsonpath可能返回列表即使只有一个值 actual_value actual_values[0] if isinstance(actual_values, list) and len(actual_values) 0 else actual_values assert actual_value expected_value, fJSON路径值断言失败路径 {json_path}: 预期 {expected_value}, 实际 {actual_value} logger.info(fJSON路径断言通过: {json_path} - {expected_value}) staticmethod def assert_response_contains(response, expected_text): 断言响应文本中包含特定字符串 if not response: raise AssertionError(响应对象为None) actual_text response.text assert expected_text in actual_text, f响应文本不包含 {expected_text} 实际响应: {actual_text[:500]} logger.info(f响应包含文本断言通过: {expected_text}) staticmethod def assert_response_time_less_than(response, threshold_seconds): 断言响应时间小于阈值 if not response: raise AssertionError(响应对象为None) actual_time response.elapsed.total_seconds() assert actual_time threshold_seconds, f响应时间过长阈值: {threshold_seconds}s, 实际: {actual_time:.2f}s logger.info(f响应时间断言通过: {actual_time:.2f}s {threshold_seconds}s)为什么需要自定义断言语义更清晰assert_status_code(response, 200)比self.assertEqual(response.status_code, 200)更贴近测试人员的思维。功能更强大assert_json_value_by_path使用了jsonpath库需要安装pip install jsonpath。JSONPath 是一种类似 XPath 的查询语言可以非常灵活地从复杂的嵌套 JSON 中提取值。例如断言resp[‘data’][‘user’][‘id’]等于 123用 JSONPath 表达式$.data.user.id就能搞定代码更简洁。更好的错误信息自定义断言可以在失败时输出更详细、更有助于调试的信息比如打印出实际的响应片段。性能断言assert_response_time_less_than是一个很好的例子它把对接口性能的监控也集成到了自动化断言中。4. 测试用例编写与数据驱动实践有了强大的工具类现在我们可以像搭积木一样编写清晰、可维护的测试用例了。这是框架价值最直接的体现。4.1 设计Excel测试用例模板首先我们需要约定好 Excel 文件的格式。通常一个工作表Sheet对应一个测试模块或一个接口。列的设计至关重要以下是一个“用户登录”接口的示例模板用例ID用例描述是否执行请求方法接口路径请求头请求参数(JSON)预期状态码预期响应字段路径预期响应字段值实际结果备注LOGIN_001用户名密码正确登录YPOST/api/v1/login{Content-Type:application/json}{username:test_user,password:123456}200$.code0LOGIN_002用户名错误YPOST/api/v1/login{Content-Type:application/json}{username:wrong,password:123456}200$.code1001LOGIN_003密码错误YPOST/api/v1/login{Content-Type:application/json}{username:test_user,password:wrong}200$.code1001LOGIN_004性能测试-响应时间YPOST/api/v1/login{Content-Type:application/json}{username:test_user,password:123456}200响应时间1s列设计解析用例ID/描述用于标识和说明用例。是否执行可以用 ‘Y’/‘N’ 来控制是否跳过该用例非常灵活。请求方法/路径定义接口。请求头/参数这里存储的是字符串。我们需要在代码中将其解析为字典或对象。对于 JSON 参数我们用json.loads()来解析。预期状态码/字段路径/字段值定义断言条件。字段路径使用 JSONPath 语法。实际结果留空由框架在运行时写入 ‘Pass’ 或 ‘Fail’。备注存放一些额外信息比如性能阈值、特殊处理说明等。4.2 编写Unittest测试类现在我们基于上面的模板在testcases/test_login.py中编写测试类。import unittest import json import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from common.request_handler import RequestHandler from utils.excel_handler import ExcelHandler from utils.assert_utils import AssertUtils from common.config import Config # 假设有一个读取基础URL的配置类 class TestLogin(unittest.TestCase): 登录模块测试用例 classmethod def setUpClass(cls): 整个测试类开始前执行一次 cls.request RequestHandler() cls.config Config() cls.base_url cls.config.get(api, base_url) # 从配置文件读取基础URL # 加载测试数据 data_file os.path.join(os.path.dirname(__file__), ../data/test_cases.xlsx) cls.excel ExcelHandler(data_file) cls.cases cls.excel.get_sheet_by_name(Login).read_data() print(f共加载到 {len(cls.cases)} 条登录测试用例。) def setUp(self): 每条测试用例开始前执行 # 可以在这里做一些初始化比如清理上一个测试的缓存 pass def test_login_normal(self): 遍历执行Excel中所有标记为执行的登录用例 for index, case in enumerate(self.cases, start2): # start2 对应Excel行号 # 检查是否执行 if case.get(是否执行, Y).upper() ! Y: print(f跳过用例: {case.get(用例ID)} - {case.get(用例描述)}) continue print(f\n开始执行用例: {case.get(用例ID)} - {case.get(用例描述)}) # 1. 准备测试数据 url self.base_url case[接口路径] method case[请求方法] # 解析请求头字符串转字典 headers json.loads(case[请求头]) if case[请求头] else {} # 解析请求参数 req_data None if case[请求参数(JSON)]: try: req_data json.loads(case[请求参数(JSON)]) except json.JSONDecodeError as e: self.fail(f用例 {case[用例ID]} 的请求参数JSON格式错误: {e}) # 2. 发送请求 if method.upper() POST: response self.request.post(url, json_datareq_data, headersheaders) elif method.upper() GET: response self.request.get(url, paramsreq_data, headersheaders) else: # 处理其他方法 PUT, DELETE 等 response self.request.send_request(method, url, json_datareq_data, headersheaders) # 3. 断言验证 result Pass try: # 断言状态码 expected_code int(case[预期状态码]) AssertUtils.assert_status_code(response, expected_code) # 如果提供了JSON路径和预期值则进行断言 json_path case.get(预期响应字段路径) expected_value case.get(预期响应字段值) if json_path and expected_value is not None: # 注意Excel中读取的数字可能是int/float字符串需要处理 # 这里简单处理将字符串123转为inttrue/false保持原样由json.loads处理 try: # 尝试将预期值转换为Python对象处理数字、布尔值、列表等 ev json.loads(expected_value) if isinstance(expected_value, str) and expected_value.startswith(({, [, t, f, n)) else expected_value # 如果ev是字符串数字尝试转为int/float if isinstance(ev, str) and ev.isdigit(): ev int(ev) elif isinstance(ev, str) and ev.replace(., , 1).isdigit(): ev float(ev) except (json.JSONDecodeError, ValueError): ev expected_value # 转换失败保持原样字符串 AssertUtils.assert_json_value_by_path(response, json_path, ev) # 性能断言示例如果备注中指定了响应时间要求 remark case.get(备注, ) if 响应时间 in remark: # 这里可以简单解析备注或单独设计一列‘最大响应时间’ AssertUtils.assert_response_time_less_than(response, 1.0) # 假设阈值1秒 except AssertionError as e: result Fail print(f用例 {case[用例ID]} 断言失败: {e}) # 可以将失败信息也写回Excel self.excel.write_result(index, 实际结果, fFail: {str(e)[:100]}) # 限制长度 except Exception as e: result Fail print(f用例 {case[用例ID]} 执行异常: {e}) self.excel.write_result(index, 实际结果, fError: {str(e)[:100]}) else: # 断言全部通过 self.excel.write_result(index, 实际结果, Pass) print(f用例 {case[用例ID]} 执行通过。) # 4. 可选将响应内容摘要也写回Excel便于查看 # if response: # self.excel.write_result(index, 实际响应摘要, response.text[:200]) def tearDown(self): 每条测试用例结束后执行 pass classmethod def tearDownClass(cls): 整个测试类结束后执行 # 保存测试结果到新文件避免污染原数据文件 report_dir os.path.join(os.path.dirname(__file__), ../reports) os.makedirs(report_dir, exist_okTrue) result_file os.path.join(report_dir, test_cases_with_result.xlsx) cls.excel.save(result_file) cls.excel.close() print(f\n所有测试用例执行完毕结果已保存至: {result_file}) if __name__ __main__: unittest.main()代码逻辑深度解析setUpClass和tearDownClass这是类级别的方法在整个测试类开始和结束时各执行一次。这里非常适合做资源初始化如创建请求对象、读取Excel和清理工作如保存结果、关闭文件。数据驱动循环test_login_normal这个测试方法实际上是一个“测试模板”。它通过遍历 Excel 中的每一行数据为每一行数据生成一个独立的测试场景。Unittest 会将其视为一个测试方法但通过我们的循环和断言实现了数据驱动的效果。灵活的断言组合代码中展示了如何根据 Excel 中是否填写了“预期响应字段路径”和“预期响应字段值”来决定是否进行 JSON 断言。这使得我们的用例模板既能做简单的状态码校验也能做复杂的业务字段校验。结果回写与错误处理在try...except块中我们捕获了断言失败 (AssertionError) 和其他异常。一旦失败就将结果标记为 ‘Fail’ 并写回 Excel同时打印错误日志。这保证了即使某个用例失败也不会影响后续用例的执行。数据类型转换的坑从 Excel 读取的数据默认可能是字符串。我们需要将字符串的状态码转为int将 JSON 格式的字符串转为字典还要处理数字字符串如”123″和布尔值字符串如”true”的转换。代码中使用了json.loads()和简单的类型判断来处理这是一个非常关键的细节处理不好会导致断言总是失败。5. 测试执行、报告生成与持续集成雏形用例写好了如何运行并看到漂亮的报告呢虽然我们这个框架轻量但报告和批量执行的能力必不可少。5.1 主运行入口与测试发现我们创建一个run_tests.py作为项目的主入口它负责发现并运行所有测试同时生成文本格式和更美观的 HTML 报告。import unittest import os import sys import time from HTMLTestRunner import HTMLTestRunner # 需要安装: pip install html-testRunner # 将项目根目录加入Python路径 project_root os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, project_root) def run_all_tests(): 发现并运行所有测试用例 # 1. 定义测试用例的目录 test_dir os.path.join(project_root, testcases) # 使用unittest.defaultTestLoader.discover自动发现测试 discover unittest.defaultTestLoader.discover(test_dir, patterntest_*.py) if not discover.countTestCases(): print(未发现任何测试用例) return # 2. 生成文本测试报告控制台输出 runner unittest.TextTestRunner(verbosity2) # verbosity2 显示详细信息 print( * 60) print(开始执行接口自动化测试...) print( * 60) start_time time.time() text_result runner.run(discover) end_time time.time() print(f\n测试总耗时: {end_time - start_time:.2f} 秒) # 3. 生成HTML测试报告 report_dir os.path.join(project_root, reports) os.makedirs(report_dir, exist_okTrue) report_file os.path.join(report_dir, fapi_test_report_{time.strftime(%Y%m%d_%H%M%S)}.html) with open(report_file, wb) as f: html_runner HTMLTestRunner( streamf, title接口自动化测试报告, description测试环境: 测试环境 | 浏览器: Chrome, verbosity2 ) html_runner.run(discover) print(f\nHTML测试报告已生成: {report_file}) # 4. 根据测试结果决定退出码 (用于CI/CD) if text_result.failures or text_result.errors: print(测试失败) sys.exit(1) # 非0退出码表示失败 else: print(所有测试用例通过) sys.exit(0) if __name__ __main__: run_all_tests()关键点说明unittest.defaultTestLoader.discover这是 Unittest 的“测试发现”神器。它会递归地在指定目录test_dir下查找所有以test_开头的.py文件并自动识别其中继承自unittest.TestCase的测试类和方法。这意味着你以后新增的测试文件只要按命名规范就会被自动加载无需修改这个运行脚本。HTMLTestRunner这是一个第三方库可以将 Unittest 的测试结果渲染成直观的 HTML 报告包含通过/失败/错误的统计、每个用例的执行详情和错误堆栈。比控制台输出更利于归档和分享。你需要先执行pip install html-testRunner安装它。退出码sys.exit(1)和sys.exit(0)非常重要。在持续集成CI工具如 Jenkins、GitLab CI中脚本的退出码会被用来判断本次构建/测试是否成功。非零退出码通常表示失败会触发报警或阻止后续流程。5.2 日志配置与问题排查日志是定位问题的眼睛。我们在common/logger.py中配置一个简单的日志器。import logging import os import sys from datetime import datetime def get_logger(name, levellogging.INFO): 获取一个配置好的logger实例 :param name: logger名称通常使用 __name__ :param level: 日志级别 :return: logger实例 logger logging.getLogger(name) logger.setLevel(level) # 避免重复添加handler if logger.handlers: return logger # 定义日志格式 formatter logging.Formatter( %(asctime)s - %(name)s - %(levelname)s - %(message)s, datefmt%Y-%m-%d %H:%M:%S ) # 控制台处理器 console_handler logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # 文件处理器按日期滚动 log_dir os.path.join(os.path.dirname(os.path.dirname(__file__)), reports/logs) os.makedirs(log_dir, exist_okTrue) log_file os.path.join(log_dir, ftest_{datetime.now().strftime(%Y%m%d)}.log) file_handler logging.FileHandler(log_file, encodingutf-8) file_handler.setLevel(logging.DEBUG) # 文件里记录更详细的DEBUG信息 file_handler.setFormatter(formatter) logger.addHandler(file_handler) return logger在RequestHandler和AssertUtils中我们都已经使用了这个日志器。运行测试后你会在reports/logs/目录下找到按日期命名的日志文件里面记录了每一个请求、响应和断言的详细信息是排查接口超时、响应错误、断言失败等问题的最重要依据。6. 常见问题、优化技巧与扩展方向框架跑起来了但在实际项目中你肯定会遇到各种各样的问题。下面是我总结的一些典型坑点和优化建议。6.1 高频问题排查清单问题现象可能原因排查步骤与解决方案读取Excel报错KeyError1. 表头名称与代码中使用的键名不匹配大小写、空格。2. Excel文件被其他程序如WPS打开并锁定。1. 打印出read_data()返回的第一行字典的键检查是否与代码中的case[‘url’]等完全一致。2. 关闭Excel文件确保文件未被占用。JSON解析错误json.decoder.JSONDecodeError1. Excel中“请求参数”或“预期值”列的JSON格式错误如缺少引号、括号不匹配。2. 接口返回的不是合法JSON可能是HTML错误页面。1. 使用在线的JSON格式校验工具检查Excel单元格内的字符串。2. 在RequestHandler的日志中查看原始的response.text确认返回内容。断言失败但肉眼看着值一样1.数据类型不一致Excel中数字123是字符串而接口返回的是整数123。2.空格或换行符Excel单元格中存在不可见字符。3. 浮点数精度问题。1. 在断言前打印双方的类型type(expected)和type(actual)。2. 使用repr()函数打印值查看隐藏字符。3. 对于浮点数使用round()或pytest.approx进行近似比较。Session未保持登录状态1. 登录接口返回的token或cookie没有被正确设置到session中。2. 每次测试都创建了新的RequestHandler实例。1. 确认登录接口的响应头中是否有Set-Cookie或响应体中有token。Requests 的 Session 会自动处理 Cookie。2. 对于 token需要在登录后手动将其添加到session.headers[‘Authorization’] ‘Bearer ‘ token。3. 确保在setUpClass中只初始化一次RequestHandler并在所有测试方法中共享这个实例。测试依赖与顺序问题后一个测试用例依赖前一个用例产生的数据如订单ID。不推荐用unittest的测试顺序来管理依赖。应该1.接口依赖在setUp中调用依赖的接口获取必要的数据如先登录获取token。2.数据准备通过调用业务接口或直接操作数据库在用例开始前创建好测试数据。3.数据清理在tearDown中清理本次测试产生的数据避免污染后续测试。大量用例执行慢1. 每个用例都重新读取Excel。2. 网络请求或响应慢。3. 日志级别太低输出过多。1. 在setUpClass中一次性读取所有用例数据到内存列表不要在循环中重复读文件。2. 考虑对查询类接口使用 Mock 或引入缓存。3. 在批量执行时将日志级别调整为WARNING或ERROR减少INFO/DEBUG日志的输出量。6.2 高级优化与扩展思路当这个基础框架满足日常需求后你可以考虑以下方向进行深化配置化管理将环境信息测试/预发/生产环境的URL、数据库连接信息、账号密码等敏感信息提取到配置文件如config.ini或config.yaml中通过Config类来读取。避免将硬编码写在代码里。测试数据工厂对于需要复杂构造的测试数据如随机的用户名、手机号、地址等可以创建一个data_factory.py利用faker库来动态生成使测试数据更真实、更多样。用例标签与筛选在 Excel 中增加一列“标签”如smoke,regression,performance然后在run_tests.py中通过自定义TestLoader或使用unittest的skipIf装饰器实现只运行冒烟测试或特定模块的测试。集成数据库校验有些业务逻辑光校验接口响应不够还需要验证数据库中的数据是否一致。可以在utils下增加一个db_handler.py封装数据库操作在测试断言环节加入数据库查询验证。对接持续集成将run_tests.py脚本放到 Jenkins 或 GitLab CI 的 Pipeline 中。每次代码提交后自动执行接口测试并将 HTML 报告作为构建产物保存。测试失败时通过退出码让构建失败并发送邮件或钉钉通知。向 Pytest 迁移当项目越来越复杂需要更灵活的夹具Fixture、参数化、插件生态时可以考虑将框架底层从 Unittest 迁移到 Pytest。Pytest 可以兼容 Unittest 的写法迁移成本相对较低却能获得更强大的功能。这个由 Python requests unittest Excel 构成的框架就像一把精心打磨的瑞士军刀它可能不是最强大、最自动化的但它简单、直接、有效能让你快速抓住接口自动化测试的核心脉络。从理解 HTTP 请求、组织测试用例、管理测试数据到编写断言和生成报告每一步你都能清晰地掌控。掌握了它你不仅拥有了一个可用的工具更建立起了一套完整的自动化测试思维这才是未来应对更复杂测试挑战的坚实基础。