做接口自动化有一段时间了大部分团队一上来就是写用例、封装断言、接入 CI很少有人会花时间认真对待项目根目录下那个pytest.ini文件。我一开始也觉得它无非就是放几个简单的命令行参数直到有一次接手一个项目用例目录混乱、标记满天飞、命令行参数十好几行才意识到配置文件这东西前期不用心后面全是账。这篇文章就把我在接口自动化项目里实际使用pytest.ini的完整经验拆开讲一讲包括每个配置项到底做什么、怎么选、怎么组合、踩过哪些坑希望能帮你把测试框架的底座打稳。1. 为什么接口自动化框架里必须有 pytest.ini先说一个最直观的场景。你写了几百条接口用例散落在api_case、testcases、cases等不同目录下每条用例都带着pytest.mark.smoke、pytest.mark.regression、pytest.mark.deploy之类的标记。假设你现在想跑testcases目录下所有标记为 smoke 的用例并且要带详细日志、跳过告警输出命令行大概率长这样pytest testcases -m smoke -v -s --tbshort --disable-warnings --strict-markers --alluredir./report/allure_results这个命令确实能跑但问题很明显参数太长、容易输错、团队成员每个人输入的参数不一致、新同事根本不知道要加--strict-markers或者--alluredir。时间久了大家每人一个跑法回归结果无法统一排查问题全靠猜。pytest.ini的核心价值就是把这类“约定”固化下来。它是 pytest 默认读取的配置文件之一优先级最高一旦存在pytest 启动时就会先加载它把里面所有配置项应用到全局。上面那串复杂的命令行参数完全可以化为几行配置之后所有人只需要执行pytest三个字母行为就完全一致。另外还有两个容易被忽视的功能。第一是标记管理。接口测试里smoke、regression、slow、dependency这些标记如果不注册pytest 运行时会给出PytestUnknownMarkWarning告警量一多日志就很脏。虽然这种告警不中断执行但配合--strict-markers时未注册标记会直接报错让你从源头规避拼写错误。第二是测试用例的发现规则。pytest 默认递归查找文件名以test_*开头或*_test.py结尾的文件但接口项目里经常有api_utils.py、test_data.py、helpers.py这类非用例文件如果不在配置里限定testpathspytest 就会全盘扫描非常影响收集速度还可能误加载不该加载的模块。从项目协作的角度看配置文件就是团队的“公共契约”。新人来了看一眼pytest.ini就知道整个项目怎么跑、有哪些标记、哪些目录被排除、测试数据和报告输出到哪而不是到处找人问。我自己的经验是一个接口自动化项目的pytest.ini一旦配置到位后期几乎不用再动真正是一劳永逸的事情。2. pytest.ini 与其它配置文件的选型对比pytest 官方支持四种配置文件pytest.ini、pyproject.toml、setup.cfg、tox.ini。很多人会纠结该用哪个我直接给结论接口自动化项目优先选 pytest.ini除非没有选择余地。几种配置文件的核心差异如下配置文件优先级适用场景注意点pytest.ini最高纯测试项目或只需要配置 pytest 的普通项目文件编码必须保存为 UTF-8否则中文注释容易乱码pyproject.toml中项目本身是 Python 包或使用 Poetry、PEP 621 管理依赖pytest 配置要写在[tool.pytest.ini_options]段落字段名略有不同setup.cfg低项目需要打包发布且还在使用 setuptools 配置pytest 配置写在[tool:pytest]段落且某些参数在旧版本上解析有问题tox.ini低使用 tox 做多环境测试或 CI 矩阵pytest 配置写在[pytest]段落通常与 tox 环境配置混在一个文件里选pytest.ini的理由很简单它解析规则最简单、官方支持最完整、没有额外的格式约定。比如pyproject.toml里要写[tool.pytest.ini_options]值必须符合 TOML 格式字符串要用引号包起来布尔值不能写true名字和 Python 里不一样容易写错setup.cfg和tox.ini更是有历史包袱某些配置项在它们上面只做“尽力支持”解析逻辑不同。你既然把精力花在接口测试上就不要再为配置文件的坑分心了直接用pytest.ini最稳妥。还要特别强调一下pytest.ini只要存在就会覆盖其它所有配置文件的 pytest 部分不需要担心和其它工具冲突。比如你项目用了setup.cfg管理代码风格pytest.ini只管 pytest两者互不干扰。这一点在大型项目里很重要测试配置和工程配置可以彻底分离出问题也容易定位。还有一个容易踩的坑pytest.ini的文件名必须严格叫pytest.ini不能叫pytest.int、pytest_conf.ini改一个字母都不行。有些同学会顺手建一个pytest.ini.txt本质上是文本文件虽然双击打开内容一样但 pytest 根本不认配置无效还没有明显提示排查起来很让人崩溃。3. 核心配置项逐项拆解与实操详解现在进入正题把pytest.ini里我认为接口自动化场景下最有用的配置项逐个讲清楚。每个配置项我都会说明它的作用、使用场景、我实际用过的配置写法以及需要注意的细节。3.1 testpaths —— 精准定位测试用例目录接口自动化项目的用例目录通常不止一个。有的是按业务线分比如testcases/order、testcases/pay、testcases/user有的是按测试类型分比如tests/api、tests/integration。如果不加testpathspytest 默认从当前目录下递归搜索结果就是一个指令扫描了所有文件速度慢不说很容易把test_data、utils目录里名字里带 test 的文件也当成用例。我的做法是在根目录统一建一个testcases目录然后配置[pytest] testpaths testcases这样 pytest 只会去testcases下找用例其它目录一概不看。如果用例分布在多个目录可以用空格分隔testpaths testcases/api testcases/integration有一个细节需要注意testpaths配置的路径必须相对于pytest.ini所在目录。很多人喜欢在testpaths里写带根目录的绝对路径比如/home/user/project/testcases这会让配置失去可移植性换个环境就要改一次。正确做法是直接用相对路径pytest 会自动基于配置文件所在目录解析。我见过一个真实案例同事把testpaths配成了testcases/带了个斜杠然后 pytest 偶尔收集不到用例。查了源码发现路径解析时末尾斜杠在部分操作平台下会导致目录拼接异常。虽然 PyPI 最新版修了这个边界但建议还是尽量别加多余的斜杠保持路径干净。3.2 markers —— 标记体系与严格校验接口测试里标记是管理用例优先级和业务场景的强大工具。一套合理的标记体系通常包括smoke冒烟用例核心链路每次提测必须跑regression回归用例全量执行slow耗时长的用例定时任务里跑deploy线上环境部署后的验证用例p1 / p2 / p3用例优先级按重要程度分层如果不注册这些标记直接使用pytest 运行时会给出黄色告警。之前我总觉得无所谓直到有一次某成员手滑把pytest.mark.smoke写成了pytest.mark.somke由于这个标记从未注册pytest 只是提示 unknown mark不会报错结果所有 smoke 用例没跑成功而 CI 日志里这种警告早就被刷掉了问题被带到了线下环境排查了半天。解决办法是把标记注册进pytest.ini并开启严格校验markers smoke: 冒烟测试核心链路用例 regression: 回归测试 slow: 耗时超过30秒的用例 p1: 一级优先级阻塞性问题 p2: 二级优先级功能性缺陷 p3: 三级优先级一般性问题然后配合命令行参数--strict-markers或者直接在 addopts 里加上这个参数。addopts --strict-markers开启之后任何未经注册的标记都会直接抛出异常用例根本不会执行。你看这才是真正解决问题的方案。它还强制团队所有成员使用统一标记避免各写各的。比如有人想加一个pytest.mark.high的标记如果没同步注册跑用例的时候立刻报错而不是悄悄地变成一条不执行用例。标记名里冒号后面的部分是标记描述接口测试中建议把描述写清楚比如明确这个标记适用哪个环境、对应哪个测试阶段。这样在生成 Allure 报告或者 CI 里筛选时别人也能看懂这个标记的用途。3.3 addopts —— 把固定参数写进默认值addopts是我最常用的配置项。它相当于给 pytest 命令注入一串默认参数相当于命令行里那些每次都要敲的参数现在统一在这里写一次。一个典型的接口自动化项目配置长这样addopts -v -s --tbshort --strict-markers --disable-warnings --maxfail5逐个解释-v详细输出每个用例的执行结果都会显示。-s允许 print 输出接口测试里经常要打印请求体、响应体不加这个参数 print 会被吞掉调试时很恼火。--tbshort发生断言失败时只输出短路信息避免报错堆栈刷屏。--strict-markers强制校验标记前面已经说过。--disable-warnings忽略 Python 警告接口测试里常见的DeprecationWarning、UnverifiedHttpsWarning非常多开着会干扰视线注意这个参数不影响strict-markers的检查两者可以并存。--maxfail5连续失败 5 条用例后终止执行适合在调试阶段快速反馈避免几百条用例全跑完才发现问题。一个很容易被忽视的问题是addopts里的参数是“默认值”如果命令行里带了相同选项命令行会覆盖它但某些情况下是“合并”而不是“覆盖”。最典型的是重复添加多个参数时比如配置里已经有-m smoke命令行又写pytest -m regression这种情况下后者会覆盖前者不会同时跑两个。而像-v和-s这类开关型参数命令行是没法“关掉”的也就是说你配置了-s任何人想在命令行关闭 print 输出都做不到这套规则需要让团队成员知道。还有一点千万别在addopts里写环境相关的路径比如--html./reports/2024-08-01这种参数应该在 CI 里动态生成后再追加写死在pytest.ini里会导致测试报告永久覆盖到同一个位置。如果你确实需要默认指定报告目录可以结合 Allure 的--clean-alluredir参数每次执行前清空旧报告避免结果混淆。3.4 norecursedirs —— 排除目录避免误收集norecursedirs其实不太起眼但接口自动化项目里作用很大。默认情况下 pytest 会忽略.git、__pycache__、venv、site-packages等常见目录但你的项目里可能还有别的目录不该被收集比如norecursedirs utils common scripts reports uploaded_files这里utils和common是工具类目录里面经常有test_connection.py这类文件名如果不排除pytest 会把它当成测试模块并尝试收集内部函数。更麻烦的是scripts目录一些部署脚本、数据初始化脚本也可能用 test 做前缀一旦被收集运行时会报错整个测试流程被中断。配置排除目录需要注意它做的是“递归排除”也就是只要某文件名匹配pytest 就不会进入该目录递归扫描。路径规则支持 glob 模式比如norecursedirs *.egg可以排除所有 egg 目录。但在接口项目里建议大家直接用目录名白名单简单可读不要玩花活的通配符降低维护心智。顺带提一句如果你把testpaths限制住了其实norecursedirs在多数情况下不会触发。但两者是配合关系testpaths是“只在这些目录里找”norecursedirs是“即使在这些目录里也不要进去”有重叠才是合理的防御性配置。我自己一般两个都会写万一以后有人改了 testpaths把scripts加进去了norecursedirs还能兜底。3.5 log_cli —— 实时观测与日志配置接口自动化调试阶段最烦的事情就是用例失败以后看不到请求参数和响应体。有些人喜欢在用例里加一堆 print然后配合-s输出但 print 的问题是它不属于结构化日志而且被-s开关控制在正式环境里策略很难统一。pytest 内置了log_cli配置开启后可以实时在终端显示日志log_cli true log_cli_level INFO log_cli_format %(asctime)s [%(levelname)s] %(name)s: %(message)s log_cli_date_format %Y-%m-%d %H:%M:%S这样在用例里用logging.info(请求响应数据: %s, resp.text)写日志运行时命令行就能实时看到。相比 printlogging 可以分级别控制正式回归时设为 WARNING 或 DEBUG调试时设为 INFO不用改代码。注意log_cli_level是全局的但你可以通过caplog在用例级别调整。比如某些关键用例我想临时看 debug 级别的日志可以在用例内部用caplog.set_level(logging.DEBUG)。这一点在实际排查问题时特别有用不需要为了单条用例重启整个执行流程。log_cli_format里的格式稍微解释一下%(name)s默认输出的是root如果你在用例模块里设置了logger logging.getLogger(__name__)它就会显示模块名定位问题时一眼就能看出日志来自哪个模块。接口自动化项目里强烈建议每个模块都显式创建 logger不要用logging.info裸调用。3.6 其他值得使用的配置项除了上面几个大头还有几个小配置项我平时也会用放在一起说minversion指定 pytest 的最低版本。接口自动化项目可能依赖pytest-assume、pytest-rerunfailures这类插件它们对 pytest 版本有要求。配置minversion 7.0后如果环境里的 pytest 版本低于 7.0会直接提示升级避免插件运行时出现莫名其妙的不兼容问题。required_plugins列出必需插件。这个配置在团队协作中很有用。比如项目依赖pytest-html、pytest-ordering、pytest-rerunfailures如果某个同事换了新环境忘记装某个插件pytest 启动时会直接报错提示缺少插件而不是运行到一半才遇到fixture xxx not found之类的诡异错误。xfail_strict当配置为 true 时标记为pytest.mark.xfail的用例如果意外通过会变成失败。接口测试里有些已知问题用 xfail 标记临时跳过但如果别人修好了问题而忘记移除标记用例就会直接报失败提醒你这对保持测试结果真实性很有帮助。console_output_style控制终端输出样式如progress或classic。CI 里我一般配置为progress大量的用例跑进度条更省日志空间本地调试时用classic更清晰。这个配置在大型项目里值得关注。4. 接口自动化实战一份可复用的 pytest.ini 模板这一章我直接给出一份我在接口自动化项目里使用的完整模板并逐段解释每个配置的意图。你可以直接复制粘贴在当前项目基础上调整路径和标记。4.1 完整模板示例[pytest] minversion 7.0 testpaths testcases norecursedirs utils common scripts reports logs data required_plugins pytest-html pytest-ordering pytest-rerunfailures # 命令行默认参数 addopts -v -s --tbshort --strict-markers --disable-warnings --maxfail10 --alluredir./report/allure_results --clean-alluredir # 标记注册 markers smoke: 冒烟测试核心链路 regression: 全量回归 slow: 耗时超过30秒 p1: 一级优先级阻塞问题 p2: 二级优先级功能缺陷 p3: 三级优先级优化建议 skip_deploy: 跳过布署环境用例 # 日志输出 log_cli true log_cli_level INFO log_cli_format %(asctime)s [%(levelname)s] %(name)s: %(message)s log_cli_date_format %Y-%m-%d %H:%M:%S # 告警过滤 filterwarnings error:.*unclosed.*:ResourceWarning ignore::DeprecationWarning动一个细节addopts采用了多行书写的方式值由空格分隔但为了可读性可以换行。pytest 会合并所有行内容当成一个字符串来处理注意不要加多余的缩进缩进在行首会被保留造成参数解析混乱。我一般保持每行顶格写或者统一四个空格缩进但不要有的有缩进有的没有。filterwarnings这行需要多说一句。它和标记类似也是可以配置多条规则的。接口自动化里最常见的告警来自 requests 库未校验 HTTPS 证书的InsecureRequestWarning如果不加处理每次执行都会有告警刷屏。可以在文件顶部或者 conftest 里用warnings.filterwarnings处理但比较优雅的方式就是在这个配置里加规则ignore:.*InsecureRequestWarning.*:Warning。上面的模板里写的是通用规则实际项目里你可以按需求增加。4.2 结合 Allure 报告的功能组合Allure 是接口自动化圈子里最常用的报告工具pytest.ini里配合 Allure 的配置有几个关键点。首先是addopts里的--alluredir./report/allure_results指定原始结果目录。这里有一个很多人忽略的细节--alluredir指定的目录必须提前创建吗答案是不用pytest-allure 插件会自动创建目录。但如果目录里有上次执行的旧结果新结果会叠加在里面报告会显示重复数据。所以我在模板里加了--clean-alluredir每次执行前自动清空旧结果。其次是结合allure生命周期建议在项目根目录下的conftest.py中定义钩子动态设置报告的标题和环境信息。配置文件无法完全替代这些钩子但pytest.ini的--alluredir是基础没有它 Allure 数据根本不会生成。注意如果你同时使用了--alluredir和--htmlxxx两套报告会同时生成互不冲突。有的团队喜欢 HTML 报告放到某个 Web 目录里共享Allure 结果保留在本地两套机制都能在pytest.ini里配置。不过我不建议把--html直接写进pytest.ini因为 HTML 报告路径往往需要动态指定时间戳更适合在 CI 命令里追加。贝壳项目里的做法是本地用 pytest.ini 默认生成 Allure 结果CI 里再专门生成 HTML 归档。4.3 自定义配置项及其读取方式pytest.ini不仅能配置 pytest 内置参数还支持自定义配置项供conftest.py或测试用例读取。这对接口自动化项目特别有用因为 base_url、环境变量、超时时间、数据库连接串这类信息放在配置文件里比硬编码在 Python 代码里方便得多。自定义配置项的写法很简单就是任意独一无二的键名[pytest] base_url http://api.example.com test_env dev timeout 10然后在conftest.py里用pytestconfig.getini(base_url)获取import pytest pytest.fixture(scopesession) def base_url(pytestconfig): return pytestconfig.getini(base_url)有一个坑需要提醒如果自定义配置项跟 pytest 内置配置项重名比如你写addopts xxx这是正常覆盖但如果写testpaths xxx又写base_url xxx注意base_url虽然不是 pytest 内置项pytest 会允许它存在但某些旧版本会对无法识别的键名给出 warning所以自定义键名一定要避免和内置关键字重名。读取自定义配置还有另一个方式是通过pytestconfig对象传入 fixture这在接口自动化场景里经常用于新建 session 级别的客户端。比如所有用例共享同一个 requests.Sessionbase_url 从配置文件读取环境变量从系统变量获取两者在conftest.py中组合。这样做的好处是切换测试环境时只需要改pytest.ini的test_env不用动任何用例代码非常契合多环境接口测试的需求。5. 常见问题与排查技巧实录配置pytest.ini的过程并不是一帆风顺的这里记录几个我实际项目和社区里高频出现的问题并给出排查思路希望对你有实际参考价值。5.1 中文注释乱码与文件编码问题pytest.ini默认使用 UTF-8 编码解析。如果你用记事本在 Windows 上创建文件默认可能是 GBK 编码文件里有中文注释pytest 读取时就会出现UnicodeDecodeError或乱码。解决办法很简单用 VS Code、Sublime、Notepad 这类编辑器保存时显式选择 UTF-8 编码不要用系统记事本默认编码。还有一种情况是文件保存为 UTF-8 with BOM 格式。pytest 的解析器对开头的 BOM 忍耐有限某些版本会报parse error。如果你在文件开头看到“看不见的字符”用十六进制查看器能发现EF BB BF这就是 BOM。直接另存为无 BOM 的 UTF-8问题就解决了。5.2 配置文件不生效如果你修改了pytest.ini但运行 pytest 时发现行为没有任何变化先检查三个地方。第一文件是否在 pytest 的 rootdir 下。pytest 启动时会从当前目录向上查找配置文件找到就停止停止位置就是 rootdir。如果你在项目子目录里执行 pytest而pytest.ini在根目录那它不一定能被识别到除非根目录路径在rootdir查找链上。最简单的排查方式是在项目根目录执行pytest --help或者执行pytest --version查看输出中是否有rootdir这一行路径和pytest.ini所在位置不一致就说明文件没被加载。第二文件是不是被命令行参数覆盖了。举个例子配置文件里写了markers但命令行用了-o markersxxx那么-o的优先级更高会覆盖配置文件的值。这类问题在集成 CI 时最容易发生CI 脚本里拼参数拼多了配置就被覆盖了。第三文件名拼写错误。这个在前面提过pytest.ini一个字母都不能错。检查方法同上先看rootdir输出了哪一行。5.3 用例收集不全或收集了预期之外的用例接口测试项目经常遇到两种情况。一是某个目录新增了用例但在 pytest 运行时并没有被收集到二是明明指定了 testpaths却发现其它目录的用例也在运行。第一种情况多半是因为testpaths路径写错了比如多了一个斜杠或者大小写不敏感的系统上写的路径没有对齐。第二种情况往往出现在pytest.ini实际没被加载时因为没加载pytest 就按默认规则从当前目录递归收集造成你感觉 testpaths 失效了。用pytest --collect-only这个命令可以快速看到实际收集了哪些用例这是排查这类问题最直接的手段。我强烈建议你改完 config 文件后跑一次pytest --collect-only -q确认收集结果符合预期再跑完整用例。5.4 ad hoc 过滤器与 addopts 互相干扰一个比较隐晦的坑是在addopts里配置了-m smoke命令行再执行pytest testcases -m regression时命令行参数会覆盖配置文件里的-m这是正常行为。但如果有人在命令行执行的是pytest -m smoke or regression那么和addopts里的-m smoke之间pytest 会使用最后出现的参数导致结果和预期完全不一样。多人协作时我建议在项目文档里明确说明不要用命令行覆盖-m参数所有用例筛选统一在pytest.ini中调整或者统一约定在 CI 里追加覆盖。5.5 关于自定义配置项的类型问题pytestconfig.getini(timeout)返回的类型永远是字符串这一点很多人会疏忽。如果你在pytest.ini里写了timeout 10拿到的就是字符串10而不是整数10。做超时计算时直接用10 1就会报 TypeError。解决办法是在 conftest 中显式转换类型import pytest pytest.fixture(scopesession) def timeout(pytestconfig): return int(pytestconfig.getini(timeout))这个细节在接口自动化里很关键因为请求超时、重试次数、并发数都是数值型配置一旦类型错了表现就是有些环境跑得好好的换个配置就报错。自定义配置项再多一点时还可以写一个统一的配置解析函数在 conftest 中一次性把所有参数转成 Python 数据类型。6. 我踩过几次坑之后的个人总结最后分享一点实际使用的心得。pytest.ini不是写一次就能永远不管的文件它会随着项目规模、团队习惯、工具链的变化而演进。比如刚开始只有 smoke 和 regression 两种标记后面加了 p1/p2/p3再后来又加了 deploy、skip_deploy这些都需要同步维护markers和addopts。我自己的经验是每次修改完配置都要跑一遍pytest --collect-only -q确认收集结果和标记筛选没被破坏。另外一个建议是把pytest.ini的变更记录写进提交信息里因为它潜移默化地影响着每一次测试执行的结果。我曾经有一次因为把--disable-warnings不小心删掉导致 CI 日志被警告刷爆真正确认问题的时间远多于改配置的时间。还有个细节算是我个人偏好的推荐尽量保持pytest.ini简洁不要把所有功能都堆进去。能用默认值就不用显式配比如log_level INFO这种如果你没特殊需求直接沿用默认即可配置越多越难排查。接口自动化框架的核心是提升效率如果配置文件反而成了障碍就本末倒置了。如果你们团队刚开始用 pytest 做接口测试我建议第一周就把pytest.ini定下来后面所有用例编写、执行、报告都以它为准。前期多花半小时把路径、标记、默认参数理清楚省下来的是后面每个成员、每次 CI 执行时反复踩坑的时间。配置文件的威力不是体现在它有多少功能而是体现在它能不能成为整个团队默认的、不需要再解释的执行标准。