接口自动化框架选型:Python+Requests+Pytest+Excel+Allure实战指南

接口自动化框架选型:Python+Requests+Pytest+Excel+Allure实战指南 做了这么多年接口自动化我见过太多项目死在框架选型这一步。有的团队听说 httpx 出来了就赶紧把 requests 换掉结果一堆老接口的兼容问题全冒出来了有的被 unittest 的 setUp、tearDown 组织方式折磨到怀疑人生却不知道 pytest 的 fixture 能省掉多少重复代码还有的为了“显得专业”硬上 YAML 做数据驱动最后用例多到没人愿意维护。这个 Python Requests Pytest Excel Allure 的组合是我这几年给多个团队搭接口自动化框架时沉淀下来的相对稳妥的一套它的核心优势并不是每一个组件都最时髦而是每一层的能力都卡在了一个平衡点上Requests 负责稳定地发 HTTP 请求Pytest 负责测试用例的组织与执行Excel 负责测试数据的承载和维护Allure 负责把执行结果变成一份团队看得懂的报告。这篇文章我会把“框架之间的对比”作为主线从选型思路、完整项目骨架到真实环境下的踩坑经验一步一步拆给你看。这套东西适合谁如果你正准备从零搭建接口自动化或者已经在用 unittest 写用例但觉得维护成本越来越高又或者你把接口用例写在代码里却发现数据和代码纠缠得没法看那么这篇文章基本就是照着你的痛点写的。整个过程中我不会只给结论会尽量把每一个对比维度背后的取舍逻辑讲清楚方便你在自己的团队里做决策。1. 先想清楚接口自动化框架到底在比什么很多人在选型的时候容易陷入“工具名气对比”的误区。今天看一篇文章说 httpx 支持 HTTP/2 很香明天又看一个视频说 unittest 是标准库不需要额外装依赖后天看到 robotframework 的关键字风格被领导夸了于是整个技术方案被各种零散信息牵着走。要避免这种状态第一步不是打开搜索引擎而是先把“我们的接口自动化项目需要框架做什么”完整盘一遍。1.1 一套接口自动化框架的五个能力模块我习惯把接口自动化框架拆成五个能力模块HTTP 客户端能力能发 GET、POST、PUT、DELETE 请求能处理 header、cookie、query 参数、请求体能设置超时和重试。测试执行能力能组织用例、支持前后置动作、能写断言、能统计通过率和失败原因。测试数据管理能力测试数据不能硬编码在脚本里要能外置、能批量维护、能切换环境。报告与可视化能力执行结果要能变成团队成员能看懂的报告而不是一堆没人打开的 log。CI 集成能力能够被命令行一键驱动能够接入 Jenkins、GitLab CI 等流水线让自动化在持续集成里稳定跑。这五个模块每一个都有多个框架可以选它们之间本来没有“谁绝对好”的关系只有“谁更适合你当前的场景和团队结构”的关系。所以整个标题里的“Python Requests Pytest Excel Allure”本质上不是一个框架而是五个位置上的五个选型。真正的对比发生在每一个位置上HTTP 客户端层选什么、测试执行层选什么、数据管理层选什么、报告层选什么。1.2 为什么这套组合在多数项目里够用我做了不少项目之后发现一个规律接口自动化的技术复杂度远低于业务复杂度。大多数企业内部接口自动化项目请求量一天也就几千次并发并不高真正难的是业务状态的串联、数据环境的稳定、断言口径的统一。也就是说性能极限通常不是瓶颈可维护性和可读性才是。在这个前提下Requests 提供的同步请求模型足够应付绝大多数接口自动化场景它的 API 设计是“人类友好”的。Pytest 的 fixture 和参数化机制可以让前后置逻辑和数据驱动变得非常清爽。Excel 虽然不是最有“技术感”的数据载体但它在团队协作里优势很大——产品和测试都能直接改不需要打开代码编辑器。Allure 则解决了测试报告“只给自己看”的问题步骤、层级、历史趋势都不需要额外开发。这套组合没有一个组件是“同类型里最强的”但组合起来非常顺手这也是我在这个标题里把“框架之间的对比”放在括号里的原因——真正值得对比的是每个位置上的选型而不是拿着一个框架去吊打另一个。2. Requests 凭什么站在 HTTP 客户端的第一梯队HTTP 客户端层是接口自动化的地基。这一层选得不好后面所有用例写起来都会很别扭。在 Python 生态里可选对象大概有 urllib、Requests、httpx、aiohttp 这么几个下面把这几个放在一起做一轮对比。2.1 urllib、Requests、httpx、aiohttp 的真实差异先看代码层面的直观感受。用 urllib 发一个带 JSON 体的 POST 请求你需要这样写import json import urllib.request data json.dumps({username: admin, password: 123456}).encode(utf-8) req urllib.request.Request( https://api.example.com/login, datadata, headers{Content-Type: application/json} ) with urllib.request.urlopen(req, timeout5) as resp: body resp.read().decode(utf-8) status resp.status用 Requests 写同样的逻辑import requests resp requests.post( https://api.example.com/login, json{username: admin, password: 123456}, timeout5 ) status resp.status_code body resp.json()同样是发一个请求代码量差距接近一半可读性差距更大。urllib 不是不能用而是在项目里会浪费大量开发时间去处理编码、响应解析、cookie 管理这些底层细节这些工作对接口用例本身没有任何正向帮助。再看 httpx 和 aiohttp。httpx 支持 HTTP/2也有异步模式但如果你的接口自动化是同步执行它的核心优势根本用不上反而多了一个学习成本。aiohttp 是异步框架性能上限高但异步代码的调试成本、团队成员熟悉程度都要考虑为了一个并发量并不高的接口自动化项目引入异步复杂度并不划算。下面这张表是我在实际项目中对比后留下的参考维度urllibRequestshttpxaiohttp上手成本中等标准库自带极低较低较高需要异步基础会话管理手动维护 CookieSession 自动处理Client 自动处理需要自己维护 session同步请求体验僵硬自然自然以异步为主HTTP/2不支持不支持支持不支持另有 h2 支持超时/重试需要手写通过 HTTPAdapter 配置类似 Requests需要手写团队替换成本低低中高2.2 Session 的工程化价值Requests 库真正拉开差距的不是单个请求怎么发而是requests.Session()这套机制。Session 会自动保存 cookie复用底层 TCP 连接还可以统一设置 headers、基础域名、超时时间。实际项目里我基本不会裸用requests.get()而是先构建一个项目级的 Session 对象import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) session requests.Session() session.headers.update({ Content-Type: application/json, User-Agent: AutoTest/1.0 }) session.verify False这个 Session 对象在后续所有用例里共享测试登录后拿到的 token 只要塞进session.headers后面的请求就都会自动带上不用每个用例手动传一遍。接口自动化里最常见的一类重复劳动就被这个机制简化掉了。需要特别提醒一句verifyFalse是因为很多企业内部测试环境是自签名证书不关掉验证用例根本跑不起来。关掉之后配合urllib3.disable_warnings去掉控制台警告这是常规做法但到了生产环境或对外服务证书校验必须打开。3. Pytest 和 unittest 的差距不在断言而在用例组织方式测试执行层是整个框架里争议最大的位置。unittest 是 Python 标准库零依赖很多老项目都在用Pytest 是第三方库装一个依赖但换来了一整套工程化能力。我的观点很明确新项目直接 Pytest没有任何犹豫的必要。3.1 fixture 是 Pytest 与 unittest 的分水岭unittest 的前置和后置靠setUp、tearDown、setUpClass、tearDownClass这些固定方法名。它的最大问题是作用域是写死的不同用例类的公共前置逻辑要么复制粘贴要么写一个公共基类去继承时间长了类继承关系乱得一塌糊涂。Pytest 的 fixture 把前后置动作变成了一种“可声明的依赖”。我举一个很常见的例子——大部分接口用例都需要一个登录后的 tokenimport pytest import requests pytest.fixture(scopesession) def login_token(): resp requests.post(https://api.example.com/login, json{username: admin, password: 123456}) assert resp.status_code 200 return resp.json()[token] def test_get_user_info(login_token): resp requests.get( https://api.example.com/user/info, headers{Authorization: fBearer {login_token}} ) assert resp.status_code 200 assert resp.json()[code] 0注意scopesession这个参数它会保证整个测试会话只登录一次token 被缓存并复用。如果这个逻辑放在 unittest 里要么用setUpClass配合类变量去实现要么干脆在模块顶层登录一次这两种做法都非常容易写坏。fixture 的依赖关系是显式的用例函数把login_token作为参数填上去它就知道自己依赖什么不需要任何类继承关系。3.2 参数化让数据驱动成为可能接口自动化的核心模式是数据驱动——测试步骤和测试数据分离。Pytest 的参数化机制是这个模式的地基import pytest pytest.mark.parametrize( username,password,expected_code, [ (admin, 123456, 0), (admin, wrong, 1001), (, 123456, 1002), ] ) def test_login_cases(username, password, expected_code): resp requests.post(/login, json{username: username, password: password}) assert resp.json()[code] expected_code一条用例函数可以跑多个数据组合测试报告里每个组合会单独显示。对比 unittest 时代常用的ddt库Pytest 的参数化是原生能力不需要额外封装装饰器也不需要担心 IDE 跳转失效。3.3 conftest 和插件生态工程化的分水岭当用例量大到一定规模一定会遇到两个问题公共的 fixture 怎么共享用例执行顺序怎么控制。Pytest 的conftest.py可以实现在不经import的情况下跨文件共享fixture。我在项目里的做法是项目根目录放一个conftest.py定义全局级别的 fixture比如日志对象、数据库连接。testcase目录下放一个conftest.py定义模块级别的 fixture比如登录 token、前置数据准备。写完 fixture 直接在测试函数签名里声明参数就能用Pytest 会自动解析。这种机制带来的好处是新成员看代码时不需要通过复杂的类继承关系去追踪“哪个数据是从哪来的”直接看函数参数就能掌握依赖。插件生态是另一个加分项。pytest-xdist用来并行执行用例、pytest-ordering控制用例顺序、pytest-assume解决断言不中断的问题、pytest-rerunfailures失败重跑——每一样都是接口自动化项目里的刚需场景而这些能力在 unittest 里都需要自己造轮子。3.4 对比一下 robotframework很多人会问 robotframework 要不要考虑。它的关键字驱动风格对非技术人员确实友好团队成员不需要懂 Python 语法就能写用例。但我个人不太建议在纯接口自动化项目里用它原因是接口自动化的用例需要大量逻辑判断、动态参数、数据拼接这些东西用关键字拼装表达起来很别扭调试也会比直接写 Python 麻烦一个量级。robotframework 更适合 UI 自动化或者业务流程类的测试场景接口自动化项目用 Pytest 就够了。4. Excel 不是土办法而是团队协作的最优解数据驱动的实质是让测试数据和测试代码分离。数据可以放在 Excel、JSON、YAML、数据库里每一种都有人用。这里先给一个结论没有绝对正确的选择只有更适合当前团队和数据形态的选择。4.1 各数据源方案的适用边界数据源可维护性可读性动态生成能力适用场景Excel高业务人员可维护高弱数据本身不变中小规模用例多字段组合的接口测试JSON中中中结构化数据嵌套较深时推荐YAML高高中配置类数据层级清晰时推荐数据库低需要额外开发维护低强可动态生成数据量大、依赖前置数据生成的场景Excel 的优势体现在协作上。大多数接口测试用例是测试人员写的不是开发人员写的如果要求测试人员维护 JSON 或者 YAML往往需要额外培训。Excel 是大家日常就在用的工具测试人员直接在表格里加一行数据、填上请求参数和期望结果用例就增加了这种维护门槛几乎为零。4.2 用 openpyxl 把 Excel 变成用例源在 Python 里读取 Excel 文件我通常使用 openpyxl。拿一个登录接口的用例表举例Excel 的列设计可以包含用例 ID、请求方法、请求 URL、请求头可空、请求体、期望状态码、期望业务码、备注。这样一个 Excel 文件就能完整描述所有登录场景的用例数据。读取和处理的封装如下import json from openpyxl import load_workbook def load_test_data(file_path, sheet_nameSheet1): wb load_workbook(file_path, data_onlyTrue) ws wb[sheet_name] rows list(ws.iter_rows(values_onlyTrue)) if not rows: return [] headers rows[0] cases [] for row in rows[1:]: if row[0] is None: continue case dict(zip(headers, row)) # 把字符串形式的 JSON 请求体转成字典 if isinstance(case.get(request_body), str) and case[request_body].strip(): case[request_body] json.loads(case[request_body]) cases.append(case) return cases这里有两个细节值得注意data_onlyTrue会让 openpyxl 读取单元格展示出来的值而不是公式本身。如果 Excel 里有函数计算出来的值这个参数能避免读到公式字符串。空行的跳过是必须的因为 Excel 表格经常会有人误按空格留下“看似空白但实际有空格”的行。4.3 把 Excel 数据安全送到 Pytest 参数化读出来的数据要和 Pytest 参数化对接常规做法是手动拼参数列表import pytest from common.excel_util import load_test_data cases load_test_data(data/test_login.xlsx, 登录用例) pytest.mark.parametrize(case, cases, ids[c[case_id] for c in cases]) def test_login_from_excel(case, login_token_prepare): resp requests.post( case[request_url], jsoncase[request_body], headerscase.get(request_headers) or {} ) assert resp.status_code case[expected_status] assert resp.json()[code] case[expected_code]ids参数让每一条用例在报告中显示 Excel 里的用例 ID比如LOGIN_001、LOGIN_002。这样测试报告里哪个用例失败了直接对照 Excel 的那一行就能定位排查成本大大降低。4.4 什么情况下放弃 ExcelExcel 不是银弹。当一个接口的请求体嵌套特别深比如三层以上的字典嵌套用 Excel 表示会让单元格内容变得非常长且难以读当测试数据需要动态计算比如时间戳、随机数、上一步接口响应值Excel 静态数据就满足不了当测试用例超过几千条Excel 的查找和更新效率也会下降。在这些场景下我建议局部调整方案动态数据在代码里用 fixture 构造静态数据继续走 Excel嵌套深的接口用 YAML 作为补充数据源。框架不一定要二选一可以按接口模块灵活切换。5. Allure 报告从“自测工具”到“团队资产”报告层是整个框架里最容易被低估的一层。很多人觉得报告只要能看通过率和失败 log 就够了但实际上一份好的测试报告会影响整个团队对自动化项目的信任度。Allure 和传统报告工具之间的差距远比表面看起来大。5.1 pytest-html、HTMLTestRunner 与 Allure 的本质差异HTMLTestRunner 是 unittest 时代的老方案早就停止维护了报告样式也停留在十年前。pytest-html 可以作为 Pytest 的替代方案使用但它生成的就是一个扁平结构的 HTML 文件用例清单、通过失败状态、错误信息。在用例量超过几百条以后这种报告的问题就暴露了——你可能需要从上到下翻半天才能定位到“失败集中在哪个模块”也没有任何历史趋势的展示。Allure 的本质不是一个“HTML 生成器”而是一套测试报告框架。它把执行过程中产生的数据先收集成中间文件再统一生成一个可交互的 Web 报告。你可以在报告里按照 epic、feature、story 三层维度去浏览用例结构可以查看每一步请求的请求体和响应体可以看历史失败趋势还可以把失败用例按 severity 分类筛选。对接口测试来说请求和响应记录尤其关键——排查问题的时候不用再回控制台翻 log。5.2 Allure 的安装与集成流程Allure 的安装分为两部分Python 库和命令行工具。先安装 Python 库pip install allure-pytest再装命令行工具。在 macOS 上可以用brew install allureWindows 上建议下载 allure commandline 的压缩包解压后把 bin 目录加入 PATH。装好后用allure --version验证。集成到 Pytest 只需要在配置文件里指定结果目录或者运行时加参数pytest testcase/ -s -v --alluredir./allure-results --clean-alluredir--clean-alluredir会在每次执行前清空旧的中间结果避免新旧报告数据混在一起。执行完成后生成并打开报告allure generate ./allure-results -o ./allure-report --clean allure open ./allure-reportgenerate命令把中间结果渲染成 Web 报告open会启动一个本地服务并在浏览器里打开。如果是在 CI 里执行把allure generate这一步放到流水线里生成后的allure-report目录就是可归档的产物。5.3 epic、feature、story、title报告层级的设计逻辑这正是“allure报告标题等级”关注的核心。Allure 报告支持四个层级的关键字从大到小依次为allure.epic对应报告里的 Epic 标签通常代表一个大的产品线或系统。allure.feature对应 Feature 标签通常代表一个功能模块。allure.story对应 Story 标签通常代表一个业务场景。allure.title对应用例的显示标题它决定用例在报告里的展示文本。实际代码片段import allure allure.epic(电商平台接口自动化) allure.feature(用户模块) allure.story(登录) allure.title(正确账号密码登录) def test_login_success(): ...执行完生成报告后可以看到报告首页按 epic 分类展示点进 epic 能看到 feature点进 feature 能看到 story最终一层层下钻到实际用例。这种层级设计的价值是把“用例执行结果”和“业务模块结构”映射在一起领导想看整体质量看 epic 层测试想排查细节直接进到用例层效率高很多。除了静态标题Allure 还支持动态标题适合在标题里带上用例名称参数allure.title(登录用例{case_id}) def test_login(case_id): ...还有allure.step(操作步骤说明)装饰器可以把复杂用例拆解成多个步骤步骤。在报告里每个步骤都能独立展开失败时可以直接定位到是“发请求”失败还是“断言校验”失败。5.4 一个小插曲Allure 中的请求响应记录接口自动化报告比 UI 自动化报告多一个核心诉求记录请求和响应。Allure 并不自动记录 requests 库的流量需要借助 fixture 或者钩子把关键信息 attach 到报告里。最简单的做法是把请求参数组装成 JSON 文本用allure.attach()挂到当前用例上import allure import json def attach_request_info(method, url, headers, body, resp): request_info { method: method, url: url, headers: headers, body: body, response_status: resp.status_code, response_body: resp.text[:2000] } allure.attach( json.dumps(request_info, ensure_asciiFalse, indent2), name请求与响应, attachment_typeallure.attachment_type.JSON )有了这个附属文件排查接口问题时就不需要去控制台里翻输出直接在 Allure 报告里看请求参数是什么、服务端返回了什么工作效率提升一个档次。6. 完整项目骨架从零到能跑前面把每个组件的前因后果讲了一遍现在把它们组合起来构建一个可以直接上手的项目骨架。这个骨架是我在实际项目里反复调整后留下的形态不追求“花活”只追求结构清楚、新成员上手快、后续扩展方便。6.1 目录结构与职责边界我推荐的目录结构如下interface_test/ ├── config/ │ ├── __init__.py │ └── settings.py # 环境切换、域名配置、全局参数 ├── common/ │ ├── __init__.py │ ├── requests_util.py # 请求封装、Session 管理、重试逻辑 │ ├── excel_util.py # Excel 数据读取 │ ├── allure_util.py # 请求响应附属信息 │ └── assert_util.py # 通用断言封装 ├── data/ │ ├── test_login.xlsx │ └── test_user.xlsx ├── testcase/ │ ├── __init__.py │ ├── conftest.py # 局部 fixture │ └── test_login.py │ └── test_user.py ├── reports/ # 生成的 HTML 报告 ├── allure-results/ # 执行产生的中间结果 ├── requirements.txt └── pytest.iniconfig目录放环境相关的配置例如不同环境的 base_url、账号信息。之所以单独建一个目录是因为接口自动化项目几乎一定会有多环境切换的需求——测试环境、预发布环境、生产环境只读接口都需要跑环境信息集中管理后切换非常方便# config/settings.py import os ENV os.getenv(TEST_ENV, test) ENV_CONFIG { test: { base_url: https://test-api.example.com, username: test_user, password: test_pass }, pre: { base_url: https://pre-api.example.com, username: pre_user, password: pre_pass } } config ENV_CONFIG[ENV]6.2 核心代码请求层、数据层、用例层common/requests_util.py是整个框架的“发动机”封装了 Session、统一超时、统一重试同时预留了日志输出的位置# common/requests_util.py import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry from config.settings import config def create_session(retry_times3, backoff_factor1): session requests.Session() session.headers.update({Content-Type: application/json}) session.verify False retry Retry( totalretry_times, backoff_factorbackoff_factor, status_forcelist[429, 500, 502, 503, 504], allowed_methods[GET, POST, PUT, DELETE] ) adapter HTTPAdapter(max_retriesretry) session.mount(https://, adapter) session.mount(http://, adapter) return session session create_session() def request(method, url, **kwargs): kwargs.setdefault(timeout, (5, 30)) full_url url if url.startswith(http) else config[base_url] url resp session.request(method, full_url, **kwargs) return resp这里需要解释几个参数的含义timeout我习惯传一个元组(5, 30)表示连接超时 5 秒、读超时 30 秒。如果只传一个数字requests 会把它同时当作连接和读超时实际使用时容易遇到“接口慢一点就超时”的误报。Retry的backoff_factor是退避因子重试等待时间按{backoff_factor} * (2 ** (retry_number - 1))计算1 秒、2 秒、4 秒这样递增。这个策略对服务器限流场景非常有用。allowed_methods需要显式写明旧版本 Retry 默认只重试 GET。如果 POST 接口在超时后没有做幂等处理重试可能引发重复提交问题这里有取舍但接口测试场景下我通常打开重试并配合 backoff同时提醒业务方接口要尽量做幂等。common/excel_util.py和common/allure_util.py的代码在前文已经给过工程上可以直接照搬。common/assert_util.py可以封装一些高频断言比如“响应体里某字段的类型”“列表长度”“时间格式”等减少测试函数里重复写断言模板的负担# common/assert_util.py def assert_code(resp, expected_code): assert resp.json().get(code) expected_code, \ f业务code断言失败, 期望: {expected_code}, 实际: {resp.json().get(code)} def assert_success(resp, success_code0): assert_code(resp, success_code)用例层testcase/test_login.py的完整代码可以照前文的写法组合起来先加载 Excel 数据再用parametrize驱动最后加上 Allure 装饰器和请求响应 attach。这样写出来的用例数据和代码分离新增一条用例只需在 Excel 里添加一行。6.3 让项目在 CI 里稳定跑起来项目能在本地跑通只是第一步要让它成为团队资产需要接入 CI 流水线。最重要的一件事是保证执行的幂等性同一个用例集在流水线里跑一百次结果应该是稳定一致的。要做到这一点关键在初始数据准备和清理。我常用的方案是针对每个测试模块在文件里造数据并记录关联 ID测试结束后调用清理接口或直接清库。比如新增用户后记录user_id模块用例跑完统一删除。这样即使某个用例失败也只会影响单次执行不会污染后续用例。另外要把 HTML 报告和 Allure 报告同时保留。Allure 报告用于全面分析HTML 报告作为轻量存档二者不是二选一。CI 集成时的流水线步骤大致如下python -m pytest testcase/ -s -v --alluredir./allure-results --clean-alluredir allure generate ./allure-results -o ./allure-report --clean7. 真实项目里踩过的三个高频坑框架搭建只是开始真正烧时间的往往是那些看起来不起眼的坑。下面这三个坑在我的项目里反复出现也是从网络热词里能看到的大家集中搜索的高频问题单独拎出来说一下。7.1 429 too many requests限流与重试策略接口自动化跑到后面一定会撞上一个状态码429 Too Many Requests。服务器为了保证稳定性会对单位时间内的请求数设置上限自动化跑批的时候很容易触发。requests 库默认情况下收到 429 不会自动重试用例直接失败而且失败原因在报告里看起来像服务器错误。我在处理这个问题时的做法是在 Session 上配置 Retry 策略把 429 和其他可重试的 5xx 状态码都放进status_forcelist同时配合退避因子让重试过程有节奏地拉开间隔。前文create_session里的配置已经包含这个逻辑。需要强调的是backoff_factor的作用它让每次重试之间的等待时间指数增长而不是直接撞成一个“重试风暴”。如果只是简单地把total设为 5代码可能在 1 秒内重试 5 次服务端限流反而更严重。实测下来接口自动化项目的重试次数设为 3、backoff_factor 设为 1是一个比较平衡的配置。另外还要区分“可重试的 429”和“不可重试的 429”如果响应头里带了Retry-After字段说明服务器明确告诉你多久后再试这种可以读取这个头做精准等待。import time import requests def request_with_retry_after(url, **kwargs): retry_times 3 for i in range(retry_times): resp requests.request(GET, url, **kwargs) if resp.status_code 429 and i retry_times - 1: wait int(resp.headers.get(Retry-After, 2)) time.sleep(wait) continue return resp return resp7.2 Excel 数据的编码、日期与空行陷阱Excel 数据驱动虽然好用但坑也密集。第一个坑是中文乱码。如果用 pandas 的read_excel或者 openpyxl 读取一般不会乱码但有人习惯先把 Excel 另存为 csv 再处理csv 文件的编码问题就会暴露。解决办法很简单不要走 csv 中间层直接用 openpyxl 读原文件。第二个坑是日期格式。Excel 的日期单元格在 openpyxl 里读取后会变成datetime.datetime对象直接用字符串拼接请求体会报错。处理方式是在读取数据时做一层类型转换from datetime import datetime, date def serialize_cell(value): if isinstance(value, (datetime, date)): return value.strftime(%Y-%m-%d %H:%M:%S) return value在load_test_data里对每个单元格套一层serialize_cell就行。第三个坑是空行和合并单元格。空行往往是因为“看起来空白实际有格式”的行如果不跳过会生成一堆空的用例合并单元格在某些场景下会只保留左上角的值。所以在读取方法里判断首列比如 case_id为空就直接跳过这行是最稳妥的做法。7.3 用例隔离token 传递和执行顺序接口用例之间天然有依赖登录之后才能获取用户信息下单之后才能查询订单。但这种依赖如果处理不当就会变成“用例必须按特定顺序执行”一旦乱序执行就全盘失败。我的经验是不要依赖用例执行顺序要依赖 fixture 的数据准备。token 获取放在 conftest 的 fixture 里每个需要登录态的用例都显式声明这个 fixture然后让 pytest 自己决定用例收集顺序。这样即使随机打乱用例每个用例在开始前都会先走一遍 fixture 获取 token不会出现“因为前面的用例没跑所以后面的用例失败了”这种问题。如果用例依赖的业务数据需要前置创建也放到 fixture 里pytest.fixture() def create_order(login_token): order_id api_create_order(login_token) yield order_id api_delete_order(login_token, order_id)yield之前的代码是前置准备yield之后是后置清理。即使用例断言失败后置清理代码也会执行不会把脏数据留到下一次运行。这一点是 pytest 的 fixture 机制非常优雅的地方unittest 的 tearDown 做不到这种“前后置动作和数据共享一体”的清爽感。最后再说一个执行顺序经验如果项目里确实有部分接口依赖上一条用例的响应数据比如登录后拿 cookie之后所有接口都依赖 cookie这种情况应该把“登录获取 cookie”放到 session 级 fixture 里而不是让测试用例去依赖某一条用例先执行。现在改用 fixture 之后我的用例文件里几乎不需要插件去控制顺序了。回过头来看这套“Python Requests Pytest Excel Allure”的框架在每一个环节都不是“唯一正确解”但它们是组合起来最稳的选项。真正让自动化框架活下来的从来不是某个组件多先进而是数据和代码是否彻底分离、报告是否让所有人看得懂、用例失败了能不能快速定位。我个人在经历了工具替换的折腾之后最大的体会是把 Requests 用到底、把 Pytest 的 fixture 吃透、把 Excel 数据设计好、给 Allure 配好层级这四件事做好已经能解决绝大多数团队 80% 的接口自动化问题。