[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-938":3,"consumer-news-interaction-938":39,"consumer-news-related-938":42},{"detail":4,"item":35},{"card":5,"schemaVersion":22,"fields":23,"content":29},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":15,"href":16,"sourceName":12,"meta":17,"metrics":19,"tags":20,"resolved":21},"NEWS_ARTICLE:938","news","NEWS_ARTICLE",938,"资讯","[Python\u002F测试] pytest：简洁、可扩展的 Python 测试框架","博客园","0 序 缘起: pytest 做单元测试 1 概述 产品介绍 定位：pytest 是一个面向 Python 的开源测试框架，主打“用最小代价写出可读性高的测试”，同时可扩展到复杂的功能测试、集成测试和端到端测试。 核心理念： 用普通 assert 表达断言，由 pytest 提供失败时的详细信息。","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910183944954-2024428266.png","","\u002Fnews\u002F938",[18],"2026",{},[],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":24,"summary":13,"description":13,"publishTime":25,"updateTime":26,"sourceUrl":27,"language":28},"千千寰宇","2026-09-10T16:47","2026-09-11T15:21:59","https:\u002F\u002Fwww.cnblogs.com\u002Fjohnnyzen\u002Fp\u002F22924008","中文",{"format":30,"policy":31,"normalized":21,"html":32,"text":33,"wordCount":34,"hasBody":21},"HTML","NEWS_CONTENT_V1","0 序\n\u003Cul>\n \u003Cli>缘起: pytest 做单元测试\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910183944954-2024428266.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n1 概述\n\u003Ch2>产品介绍\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cp>\u003Cstrong>定位\u003C\u002Fstrong>：pytest 是一个面向 Python 的开源测试框架，主打“\u003Cstrong>用最小代价写出可读性高的测试\u003C\u002Fstrong>”，同时\u003Cstrong>可扩展到复杂的功能测试\u003C\u002Fstrong>、\u003Cstrong>集成测试\u003C\u002Fstrong>和\u003Cstrong>端到端测试\u003C\u002Fstrong>。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>核心理念\u003C\u002Fstrong>：\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>用普通 \u003Ccode>assert\u003C\u002Fcode> 表达\u003Cstrong>断言\u003C\u002Fstrong>，由 \u003Ccode>pytest\u003C\u002Fcode> 提供\u003Cstrong>失败时的详细信息\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>用 \u003Ccode>fixture\u003C\u002Fcode>(固定支具\u002F固定夹具) 管理\u003Cstrong>测试前置条件和清理逻辑\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>用 markers、parametrize、plugins \u003Cstrong>组织和扩展测试\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>保持与 \u003Ccode>unittest\u003C\u002Fcode> 生态的兼容，允许\u003Cstrong>渐进式迁移\u003C\u002Fstrong>。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>解决的核心问题\u003C\u002Fstrong>：\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>降低测试代码编写成本。\u003C\u002Fli>\n   \u003Cli>减少传统 \u003Ccode>unittest\u003C\u002Fcode> 中大量的样板代码。\u003C\u002Fli>\n   \u003Cli>统一测试发现、参数化、跳过、失败报告和插件扩展机制。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>URLs\u003C\u002Fstrong>：\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>官网：\u003Ca href=\"https:\u002F\u002Fpytest.org\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fpytest.org\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>文档：\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>GitHub：\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>PyPI：\u003Ca href=\"https:\u002F\u002Fpypi.org\u002Fproject\u002Fpytest\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fpypi.org\u002Fproject\u002Fpytest\u002F\u003C\u002Fa>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>当前项目状态\u003C\u002Fh3>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>指标\u003C\u002Fth>\n   \u003Cth>数据\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>最新版本\u003C\u002Ftd>\n   \u003Ctd>\u003Ccode>9.1.1\u003C\u002Fcode>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>最新版本发布时间\u003C\u002Ftd>\n   \u003Ctd>2026-06-19\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>支持的 Python 版本\u003C\u002Ftd>\n   \u003Ctd>Python \u003Ccode>3.10+\u003C\u002Fcode>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>License\u003C\u002Ftd>\n   \u003Ctd>MIT\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>GitHub Stars\u003C\u002Ftd>\n   \u003Ctd>14,493\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>GitHub Forks\u003C\u002Ftd>\n   \u003Ctd>3,354\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Open Issues\u002FPRs\u003C\u002Ftd>\n   \u003Ctd>815\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>最近一次推送\u003C\u002Ftd>\n   \u003Ctd>2026-09-08\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cblockquote>\n \u003Cp>数据来源：GitHub API、PyPI API，查询时间为 2026-09-10。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>发展历程\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Ccode>pytest\u003C\u002Fcode> 的历史较长，官方 README 中标注的项目版权起点为 \u003Cstrong>2004 年\u003C\u002Fstrong>，\u003Cstrong>现代版本的公开发布记录\u003C\u002Fstrong>可以从 1.0 之后清晰追溯。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>时间\u003C\u002Fth>\n   \u003Cth>事件\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>2004\u003C\u002Ftd>\n   \u003Ctd>项目版权起点，早期与 \u003Ccode>py\u003C\u002Fcode> \u002F \u003Ccode>py.test\u003C\u002Fcode> 生态相关。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2009-08-04\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>1.0.0\u003C\u002Fcode>，现代 pytest 版本线开始清晰化。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2010-11-25\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>2.0.0\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2016-08-19\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>3.0.0\u003C\u002Fcode>；官方文档提到该阶段引入了更清晰的弃用与兼容策略。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2018-11-14\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>4.0.0\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2019-06-29\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>5.0.0\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2020-07-28\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>6.0.0\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2022-02-04\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>7.0.0\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2024-01-27\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>8.0.0\u003C\u002Fcode>，最低支持 Python 3.8。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2025-11-05\u003C\u002Ftd>\n   \u003Ctd>发布 \u003Ccode>9.0.0\u003C\u002Fcode>，最低支持提升到 Python 3.10。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2026-06-19\u003C\u002Ftd>\n   \u003Ctd>发布当前最新版本 \u003Ccode>9.1.1\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>从\u003Cstrong>长期演进\u003C\u002Fstrong>看，pytest 的主线是：\u003C\u002Fp>\n\u003Col>\n \u003Cli>保持测试写法简单。\u003C\u002Fli>\n \u003Cli>强化 fixture 和插件体系。\u003C\u002Fli>\n \u003Cli>逐步收紧兼容边界。\u003C\u002Fli>\n \u003Cli>跟随 Python 版本演进。\u003C\u002Fli>\n \u003Cli>维持庞大的第三方插件生态。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>主要功能\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cp>\u003Cstrong>自动测试发现\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>默认收集 \u003Ccode>test_*.py\u003C\u002Fcode>、\u003Ccode>*_test.py\u003C\u002Fcode> 文件。\u003C\u002Fli>\n   \u003Cli>默认收集 \u003Ccode>test_*\u003C\u002Fcode> 函数和 \u003Ccode>Test*\u003C\u002Fcode> 类。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>增强断言\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>使用普通 \u003Ccode>assert\u003C\u002Fcode>。\u003C\u002Fli>\n   \u003Cli>失败时自动输出上下文。\u003C\u002Fli>\n   \u003Cli>支持 \u003Ccode>pytest.approx\u003C\u002Fcode> 做近似比较。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>Fixture 依赖注入\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>测试通过\u003Cstrong>函数参数声明依赖\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>支持分层、复用、缓存和 teardown。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>参数化测试\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>@pytest.mark.parametrize\u003C\u002Fcode> 可将一组输入展开成多个测试用例。\u003C\u002Fli>\n   \u003Cli>支持组合参数、pytest.param、id、marks。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>Markers\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>支持内置 marker 和自定义 marker。\u003C\u002Fli>\n   \u003Cli>可用 \u003Ccode>-m\u003C\u002Fcode> 进行测试选择。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>插件体系\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>内置插件。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>conftest.py\u003C\u002Fcode> 本地插件。\u003C\u002Fli>\n   \u003Cli>第三方插件生态。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>测试输出控制\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>-v\u003C\u002Fcode>、\u003Ccode>-q\u003C\u002Fcode>、\u003Ccode>-s\u003C\u002Fcode>、\u003Ccode>--tb=short\u003C\u002Fcode> 等。\u003C\u002Fli>\n   \u003Cli>支持 \u003Ccode>capsys\u003C\u002Fcode>、\u003Ccode>caplog\u003C\u002Fcode>、\u003Ccode>tmp_path\u003C\u002Fcode> 等内置 fixture。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>跳过与预期失败\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>skip\u003C\u002Fcode>、\u003Ccode>skipif\u003C\u002Fcode>、\u003Ccode>xfail\u003C\u002Fcode>。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>兼容 \u003Ccode>unittest\u003C\u002Fcode>\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>可以直接运行大量已有 \u003Ccode>unittest.TestCase\u003C\u002Fcode> 测试。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>报告与调试\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>\u003Ccode>--durations\u003C\u002Fcode> 输出耗时。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>--pdb\u003C\u002Fcode> 在失败处进入调试器。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>核心优势\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>上手成本低\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>一个普通 Python 函数加一个 \u003Ccode>assert\u003C\u002Fcode> 就是一个测试。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>断言体验好\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>不需要记忆大量 \u003Ccode>self.assertEqual\u003C\u002Fcode> 之类的 API。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>fixture 系统强\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>相比传统 setup\u002Fteardown，fixture 更适合组合、复用和分层。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>参数化能力强\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>一段测试逻辑可以自动展开成多组用例。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>生态极大\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>官方 README 提到外部插件超过 1300 个。\u003C\u002Fli>\n   \u003Cli>常见方向包括覆盖率、并行执行、Django、asyncio、mock、数据库等。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>与 \u003Ccode>unittest\u003C\u002Fcode> 兼容\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>老 项目可以渐进迁移，不必一次性重写。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>社区成熟\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>长期维护、版本发布稳定、贡献者众多。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>适合 CI\u002FCD\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>命令行接口稳定，易于接入 GitHub Actions、GitLab CI、Jenkins 等系统。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>主要短板\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>【插件组合】可能带来维护成本\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>多个第三方插件同时修改收集、报告、fixture 行为时，可能产生兼容性问题。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>【隐式行为】较多\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>fixture、autouse、conftest、marker 组合可能降低代码可读性。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>复杂测试的【可读性】依赖团队约定\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>如果滥用高作用域 fixture，测试之间可能出现隐性共享状态。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>【并行执行】并非完全内置\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>官方核心不内置完整并行调度，通常需要 \u003Ccode>pytest-xdist\u003C\u002Fcode>。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对【非常规测试场景】需要插件\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>例如 Web UI 测试、数据库快照、分布式环境等，通常要结合其他工具或插件。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>大型测试套件收集【成本】可能上升\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>项目很大时，应控制 \u003Ccode>testpaths\u003C\u002Fcode>、导入路径和插件加载范围。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>局限性\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>仅是【测试框架】，不是【完整的测试管理平台】\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>不内置测试计划、需求映射、用例评审等产品级功能。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>不能解决所有【测试设计】问题\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>fixture 再强，也不能替代良好的测试分层和领域建模。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对 Python 版本有要求\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>当前 \u003Ccode>9.x\u003C\u002Fcode> 只支持 \u003Ccode>Python 3.10+\u003C\u002Fcode>。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>非 Python 生态支持有限\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>虽然可以通过插件扩展，但核心优势仍在 Python 项目中。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>内部 API 不适合【直接依赖】\u003C\u002Fstrong>\n  \u003Cul>\n   \u003Cli>第三方插件应尽量使用公开 API 和 hook，避免绑定内部实现细节。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>适用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>Python 库或应用的\u003Cstrong>单元测试\u003C\u002Fstrong>。\u003C\u002Fli>\n \u003Cli>服务端 API 集成测试。\u003C\u002Fli>\n \u003Cli>数据库访问层测试。\u003C\u002Fli>\n \u003Cli>CLI 工具测试。\u003C\u002Fli>\n \u003Cli>数据处理管道测试。\u003C\u002Fli>\n \u003Cli>需要大量参数化场景的算法测试。\u003C\u002Fli>\n \u003Cli>需要渐进迁移的 \u003Ccode>unittest\u003C\u002Fcode> 项目。\u003C\u002Fli>\n \u003Cli>CI\u002FCD 中的自动化回归测试。\u003C\u002Fli>\n \u003Cli>需要利用插件生态扩展覆盖率、并行、mock、日志等能力的项目。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Chr>\n\u003Cp>不适合或需要谨慎使用的场景：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>测试之间强依赖、共享大量全局状态。\u003C\u002Fli>\n \u003Cli>希望只靠框架解决测试质量和测试分层问题。\u003C\u002Fli>\n \u003Cli>期望一个工具同时承担测试管理、用例设计和缺陷跟踪。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>同类竞品\u003C\u002Fh2>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>竞品\u003C\u002Fth>\n   \u003Cth>定位\u003C\u002Fth>\n   \u003Cth>与 pytest 的关系\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>unittest\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>Python 标准库测试框架\u003C\u002Ftd>\n   \u003Ctd>更稳定、零依赖，但样板代码更多；pytest 可运行大部分 unittest 测试。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>nose2\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>unittest 扩展\u003C\u002Ftd>\n   \u003Ctd>曾经流行，但生态活跃度和插件生态弱于 pytest。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>doctest\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>文档示例测试\u003C\u002Ftd>\n   \u003Ctd>适合校验文档示例，不适合作为完整测试框架。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>Robot Framework\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>关键字驱动验收测试\u003C\u002Ftd>\n   \u003Ctd>更偏业务流程和 E2E，Python 编程能力不如 pytest 灵活。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>Hypothesis\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>属性测试\u003C\u002Ftd>\n   \u003Ctd>可与 pytest 结合，属于互补关系。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>tox\u003C\u002Fcode> \u002F \u003Ccode>nox\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>多环境管理\u003C\u002Ftd>\n   \u003Ctd>不是测试 runner 本身，常与 pytest 搭配使用。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>自研测试框架\u003C\u002Ftd>\n   \u003Ctd>企业内部定制\u003C\u002Ftd>\n   \u003Ctd>官方生态通常更成熟，自研成本高。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2>发展趋势\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cp>\u003Cstrong>社区活跃度高\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>截至 2026-09-10，最近一次推送为 2026-09-08。\u003C\u002Fli>\n   \u003Cli>2026 年内已发布多个版本，最新为 \u003Ccode>9.1.1\u003C\u002Fcode>。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>Star \u002F Fork 规模大\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>GitHub Stars 当前为 14,493，Forks 为 3,354。\u003C\u002Fli>\n   \u003Cli>Star 趋势图：\u003Ca href=\"https:\u002F\u002Fapi.star-history.com\u002Fsvg?repos=pytest-dev\u002Fpytest&amp;type=Date\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Star History\u003C\u002Fa>\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>生态继续向工程化扩展\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>覆盖率、并行、异步、数据库、Web、云环境等方向依赖插件生态快速发展。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>兼容策略更清晰\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>官方维护了较明确的弃用、迁移和兼容政策。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>总结\u003C\u002Fstrong>\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>pytest 已经从“更好用的 unittest 替代品”发展成 Python 测试生态的事实标准，未来重点会继续放在兼容性、插件生态和大规模工程化上。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n\u003C\u002Ful>\n2 工作原理与架构\n\u003Ch2>概念术语\u003C\u002Fh2>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>术语\u003C\u002Fth>\n   \u003Cth>含义\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>test item\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>一条可执行的测试用例。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>collector\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>负责发现和生成测试项的对象。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>fixture\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>为测试提供前置状态、资源或环境的机制。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>scope\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>fixture 的生命周期范围。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>marker\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>附加在测试上的元数据。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>parametrize\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>把一组参数展开为多个测试用例。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>conftest.py\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>目录级配置和本地插件文件。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>plugin\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>扩展 pytest 行为的模块或包。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>hook\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>插件介入 pytest 生命周期的切入点。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>assertion rewriting\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>pytest 通过 AST 重写增强普通 \u003Ccode>assert\u003C\u002Fcode> 的失败信息。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2>架构与运行原理\u003C\u002Fh2>\n\u003Cp>pytest 的核心流程可以概括为：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>解析\u003C\u002Fstrong>命令行参数和配置文件。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>加载\u003C\u002Fstrong>内置插件、第三方插件和 \u003Ccode>conftest.py\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>构建\u003C\u002Fstrong>【测试收集树】。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>解析\u003C\u002Fstrong>【测试依赖】，包括 fixture、marker、parametrize。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>执行\u003C\u002Fstrong>【测试用例】。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>收集\u003C\u002Fstrong>断言结果和输出。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>执行\u003C\u002Fstrong> teardown。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>生成\u003C\u002Fstrong>报告、缓存和退出码。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>结构示意：\u003C\u002Fp>\n\u003Cdiv>\n flowchart LR A[CLI \u002F config] --&gt; B[Plugin manager] B --&gt; C[Hooks] C --&gt; D[Test collection tree] D --&gt; E[Fixture resolution] E --&gt; F[Run test item] F --&gt; G[Assert \u002F report] F --&gt; H[Teardown] G --&gt; I[Cache \u002F exit code]\n\u003C\u002Fdiv>\n\u003Ch3>运行阶段\u003C\u002Fh3>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>阶段\u003C\u002Fth>\n   \u003Cth>主要动作\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>启动\u003C\u002Ftd>\n   \u003Ctd>解析 \u003Ccode>pytest.ini\u003C\u002Fcode>、\u003Ccode>pyproject.toml\u003C\u002Fcode>、命令行参数。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>插件加载\u003C\u002Ftd>\n   \u003Ctd>注册内置插件、外部插件和 \u003Ccode>conftest.py\u003C\u002Fcode>。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>收集\u003C\u002Ftd>\n   \u003Ctd>根据命名规则和配置收集测试。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>解析\u003C\u002Ftd>\n   \u003Ctd>分析 fixture、marker、parametrize。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>执行\u003C\u002Ftd>\n   \u003Ctd>调用测试函数或测试类方法。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>报告\u003C\u002Ftd>\n   \u003Ctd>汇总 pass、fail、skip、xfail、warning。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>清理\u003C\u002Ftd>\n   \u003Ctd>逆序执行 fixture teardown。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>退出\u003C\u002Ftd>\n   \u003Ctd>返回 0 到 6 的退出码。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3>fixture 生命周期\u003C\u002Fh3>\n\u003Cp>pytest 默认 fixture 作用域是 \u003Ccode>function\u003C\u002Fcode>。常见作用域包括：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Ccode>function\u003C\u002Fcode>：每个测试函数独立创建和销毁。\u003C\u002Fli>\n \u003Cli>\u003Ccode>class\u003C\u002Fcode>：同一个测试类内共享。\u003C\u002Fli>\n \u003Cli>\u003Ccode>module\u003C\u002Fcode>：同一个测试模块内共享。\u003C\u002Fli>\n \u003Cli>\u003Ccode>package\u003C\u002Fcode>：同一个包内共享。\u003C\u002Fli>\n \u003Cli>\u003Ccode>session\u003C\u002Fcode>：整个测试会话内共享。\u003C\u002Fli>\n\u003C\u002Ful>\n3 使用指南\n\u003Ch2>安装部署\u003C\u002Fh2>\n\u003Ch3>Linux\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>python3 -m pip install -U pytest\npython3 -m pytest --version\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Windows\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>py -m pip install -U pytest\npy -m pytest --version\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>例如:\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>(base) PS F:\\Codes-Minor\\Github\\ai-chatbot&gt; .\\.venv\\Scripts\\python -m pytest --version\npytest 8.4.2\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>如果不使用 \u003Ccode>py\u003C\u002Fcode> 启动器，也可以使用：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>python -m pip install -U pytest\npython -m pytest --version\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>推荐项目结构 (必读)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>project\u002F\n├── src\u002F\n│   └── your_package\u002F\n├── tests\u002F\n│   ├── conftest.py\n│   ├── test_unit.py\n│   └── test_integration.py\n├── pyproject.toml\n└── README.md\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910164127434-814134604.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Ch2>常见配置\u003C\u002Fh2>\n\u003Ch3>\u003Ccode>requirements-dev.txt\u003C\u002Fcode> 示例\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>-r requirements.txt\n...\npytest&gt;=8.3.0,&lt;9\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910164249619-33732634.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Ch3>\u003Ccode>pyproject.toml\u003C\u002Fcode> 示例\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>[tool.pytest.ini_options]\nminversion = \"8.0\"\naddopts = \"-ra --strict-markers\"\ntestpaths = [\"tests\"]\nmarkers = [\n  \"slow: marks tests as slow\",\n  \"integration: requires external services\",\n]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>常用配置含义：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Ccode>testpaths\u003C\u002Fcode>：限制默认收集范围，提升速度。\u003C\u002Fli>\n \u003Cli>\u003Ccode>addopts\u003C\u002Fcode>：为每次运行追加默认参数。\u003C\u002Fli>\n \u003Cli>\u003Ccode>--strict-markers\u003C\u002Fcode>：未注册 marker 直接报错。\u003C\u002Fli>\n \u003Cli>\u003Ccode>markers\u003C\u002Fcode>：显式注册自定义 marker，减少拼写错误。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>常见用法\u003C\u002Fh2>\n\u003Ch3>基本测试 (必读)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode># tests\u002Ftest_basic.py\n\ndef inc(x):\n    return x + 1\n\ndef test_answer():\n    assert inc(3) == 4\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>运行：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>pytest tests\u002Ftest_basic.py\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>或：\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>python -m pytest tests\u002Ftest_basic.py\n\n或 .\\.venv\\Scripts\\python -m pytest tests\u002Ftest_basic.py -v\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>\u003Ccode>-v\u003C\u002Fcode> 参数 : \u003Ccode>--verbose\u003C\u002Fcode> 参数的缩写，表示 让 pytest 以更详细的方式输出测试结果。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>不加 \u003Ccode>-v\u003C\u002Fcode> 时，输出通常比较简洁：\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>tests\u002Ftest_basic.py .                    [100%]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>加 \u003Ccode>-v\u003C\u002Fcode> 后，会显示完整的测试路径和测试名：\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>tests\u002Ftest_basic.py::test_answer PASSED  [100%]\ntests\u002Ftest_basic.py::test_eval PASSED    [100%]\ntests\u002Ftest_basic.py::test_login FAILED   [100%]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>相关参数: \u003Ccode>-q\u002F-v\u002F-vv\u002F-s\u003C\u002Fcode> (必读)\u003C\u002Fh4>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>参数\u003C\u002Fth>\n   \u003Cth>含义\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-q\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>quiet，简化输出；只看整体结果时用。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-v\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>verbose，显示每个测试的完整 ID 和状态。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-vv\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>更详细；例如显示更完整的断言差异。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-s\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>显示测试里的 \u003Ccode>print()\u003C\u002Fcode> 输出。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cul>\n \u003Cli>\u003Ccode>-s\u003C\u002Fcode> 参数的示例\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910170204007-955301371.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Ch3>运行指定测试:文件\u002F函数\u002F类\u002F类方法 (必读)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode># 按文件\u002FFile\npytest tests\u002Ftest_api.py\n\n# 按函数\u002FFunction\npytest tests\u002Ftest_api.py::test_create_user\n\n# 按类\u002FClass\npytest tests\u002Ftest_api.py::TestUsers\n\n# 按类方法\u002FClass::Method\npytest tests\u002Ftest_api.py::TestUsers::test_create_user\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>按表达式筛选(\u003Ccode>-k\u003C\u002Fcode>)\u003C\u002Fh3>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910170926733-249722399.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pytest -k \"login and not slow\"\n   # 注: 运行测试 ID 中包含 login 的测试，但排除名称中包含 slow 的测试。这里的“【名称】”包括：文件名、类名、函数名。\n\npytest -k \"TestUsers or test_create\"\npytest -k \"not integration\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>注意：\u003Ccode>-k\u003C\u002Fcode> 参数匹配的是“\u003Cstrong>测试名称\u003C\u002Fstrong>”里的关键词，不是 \u003Ccode>marker\u003C\u002Fcode>。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>\u003Ccode>-k\u003C\u002Fcode> vs \u003Ccode>-m\u003C\u002Fcode>(marker)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>参数\u003C\u002Fth>\n   \u003Cth>匹配对象\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-k\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>文件名、类名、函数名中的关键词\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>-m\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>\u003Ccode>@pytest.mark.xxx\u003C\u002Fcode> 定义的 marker\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3>按 marker 筛选\u003C\u002Fh3>\n\u003Ch4>定义、使用\u003C\u002Fh4>\n\u003Cul>\n \u003Cli>\u003Ccode>Marker\u003C\u002Fcode> 是 pytest 里的\u003Cstrong>“测试标签\u002F元数据”机制\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>核心用途：给测试打标记，然后可以按标记筛选、跳过、归类，或让插件识别这些标签并做出特殊处理。\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>例如:\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.mark.slow\ndef test_login():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cblockquote>\n   \u003Cblockquote>\n    \u003Cp>这句话的意思不是“这个测试会自动变慢”，而是：\u003Cstrong>给 \u003Ccode>test_login\u003C\u002Fcode> 这个测试用例打上一个名为 \u003Ccode>slow\u003C\u002Fcode> 的标签。\u003C\u002Fstrong>\u003C\u002Fp>\n   \u003C\u002Fblockquote>\n  \u003C\u002Fblockquote>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>之后可以在命令行按 marker 筛选：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>pytest -m slow          # 只运行带 slow 标记的测试\npytest -m \"not slow\"    # 运行所有不带 slow 标记的测试\npytest -m \"live and not slow\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>2种写法\u003C\u002Fh4>\n\u003Cul>\n \u003Cli>写法1: \u003Ccode>pytest.mark.slow\u003C\u002Fcode>：标记单个测试\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.mark.slow\ndef test_login():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>含义： \u003Ccode>test_login\u003C\u002Fcode> 这个测试被标记为 \u003Ccode>slow\u003C\u002Fcode>。\u003Cbr>\n    运行：\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>pytest -m slow\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cblockquote>\n   \u003Cblockquote>\n    \u003Cp>就会选中它。\u003C\u002Fp>\n   \u003C\u002Fblockquote>\n  \u003C\u002Fblockquote>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>写法2：\u003Ccode>pytestmark = pytest.mark.live\u003C\u002Fcode>：标记整个模块\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest\n\npytestmark = pytest.mark.live\n\ndef test_login():\n    ...\n\ndef test_logout():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>含义：\u003Cstrong>当前这个测试模块里的所有测试，都会被标记为 \u003Ccode>live\u003C\u002Fcode>\u003C\u002Fstrong>。\u003C\u002Fli>\n  \u003C\u002Ful>\n  \u003Cblockquote>\n   \u003Cp>等价于给模块里的每个测试都打上 live 标签：\u003C\u002Fp>\n  \u003C\u002Fblockquote>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>@pytest.mark.live\ndef test_login():\n    ...\n\n@pytest.mark.live\ndef test_logout():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>\u003Ccode>live\u003C\u002Fcode> marker = 通常是团队自定义的 marker\u003C\u002Fh4>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910171846374-359180967.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cul>\n \u003Cli>通常 \u003Ccode>live\u003C\u002Fcode> 是团队自定义的 marker，常见含义是：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>需要\u003Cstrong>真实外部环境\u003C\u002Fstrong>；\u003C\u002Fli>\n   \u003Cli>会访问\u003Cstrong>真实网络\u003C\u002Fstrong>或\u003Cstrong>真实数据库\u003C\u002Fstrong>；\u003C\u002Fli>\n   \u003Cli>\u003Cstrong>运行成本\u003C\u002Fstrong>高、可能有\u003Cstrong>副作用\u003C\u002Fstrong>；\u003C\u002Fli>\n   \u003Cli>【不适合】默认在\u003Cstrong>单元测试\u003C\u002Fstrong>中跑。\u003Cbr>\n     例如：\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode># 默认不跑需要真实环境的测试。\npytest -m \"not live\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>内置 marker vs 自定义 marker\u003C\u002Fh4>\n\u003Cblockquote>\n \u003Cp>pytest 自带一些有特殊行为的 marker，例如：\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>Marker\u003C\u002Fth>\n   \u003Cth>含义\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.skip\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>无条件跳过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.skipif\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>条件跳过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.xfail\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>预期失败\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.parametrize\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>参数化测试\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.usefixtures\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>显式使用 fixture\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest.mark.filterwarnings\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>过滤警告\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cul>\n \u003Cli>而 \u003Ccode>live\u003C\u002Fcode>、\u003Ccode>slow\u003C\u002Fcode>、\u003Ccode>serial\u003C\u002Fcode> 通常是\u003Cstrong>项目自定义标签\u003C\u002Fstrong>。它们默认只是元数据，不会自动改变测试行为；需要配合 \u003Ccode>-m\u003C\u002Fcode>、插件或 hook 使用。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cp>建议在配置里注册，例如：\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>[tool.pytest.ini_options]\nmarkers = [\n    \"live: tests that require a live external environment\",\n    \"slow: tests that are slow to run\",\n]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>注册后，pytest 能识别这些 marker，避免“未知 marker”警告。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Ch3>常用运行参数 （必读）\u003C\u002Fh3>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>命令\u003C\u002Fth>\n   \u003Cth>作用\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest -v\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>显示更详细的测试名。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest -q\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>安静模式。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest -s\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>禁用输出捕获，直接显示 print。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest -x\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>首个失败后停止。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --maxfail=3\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>失败 3 次后停止。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --tb=short\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>简短 traceback。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --tb=long\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>详细 traceback。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --durations=10\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>显示最慢的 10 个测试。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --pdb\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>失败后进入调试器。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --collect-only -q\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>只收集不执行。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --fixtures\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>查看可用 fixture。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>pytest --markers\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>查看可用 marker。\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3>基础 fixture (依赖注入机制) —— 提前准备数据与资源\u003C\u002Fh3>\n\u003Cul>\n \u003Cli>\u003Ccode>@pytest.fixture\u003C\u002Fcode> 是 \u003Ccode>pytest\u003C\u002Fcode> 的 \u003Cstrong>依赖注入机制\u003C\u002Fstrong>，用来为测试\u003Cstrong>准备它们需要的资源或状态\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>通俗理解：\u003Ccode>@pytest.fixture\u003C\u002Fcode> 就是把“\u003Cstrong>测试的前置准备\u003C\u002Fstrong>”变成\u003Cstrong>可复用的依赖\u003C\u002Fstrong>；测试只要写参数名，\u003Ccode>pytest\u003C\u002Fcode> 负责\u003Cstrong>准备和注入\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>核心规则： 测试函数声明什么参数名，pytest 就自动找\u003Cstrong>同名 fixture\u003C\u002Fstrong>，执行它并把返回值注入进来。例如：\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.fixture\ndef user():\n    return {\"id\": 1, \"name\": \"Alice\"}\n\ndef test_user_name(user):\n    assert user[\"name\"] == \"Alice\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>这里的 \u003Ccode>user\u003C\u002Fcode> 不是\u003Cstrong>普通参数\u003C\u002Fstrong>，而是由 \u003Ccode>fixture\u003C\u002Fcode> 机制提供的测试数据。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>常见用途：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>构造测试对象\u003C\u002Fli>\n   \u003Cli>创建数据库\u002FHTTP 客户端\u003C\u002Fli>\n   \u003Cli>准备临时目录\u003C\u002Fli>\n   \u003Cli>初始化日志、配置\u003C\u002Fli>\n   \u003Cli>在测试后清理资源\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>例如:\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>@pytest.fixture\ndef client():\n    app = create_app()\n    with app.test_client() as c:\n        yield c\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>fixture teardown（测试后清理机制）\u003C\u002Fh3>\n\u003Cul>\n \u003Cli>\u003Ccode>fixture teardown\u003C\u002Fcode> 是 pytest 的 \u003Cstrong>测试后清理机制\u003C\u002Fstrong>，用于\u003Cstrong>在测试完成后释放资源、恢复状态\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>\u003Ccode>tear down\u003C\u002Fcode> : 英译: 拆卸、销毁\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>核心写法：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.fixture\ndef db():\n    conn = connect_db()\n    yield conn          # 交给测试使用\n    conn.close()        # teardown 机制：测试结束后执行\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>执行顺序：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Col>\n   \u003Cli>执行 fixture 前半段，创建资源。\u003C\u002Fli>\n   \u003Cli>\u003Ccode>yield conn\u003C\u002Fcode> 把资源注入测试。\u003C\u002Fli>\n   \u003Cli>测试运行。\u003C\u002Fli>\n   \u003Cli>执行 \u003Ccode>yield\u003C\u002Fcode> 后面的清理代码。\u003C\u002Fli>\n  \u003C\u002Fol>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>常见清理内容：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>关闭数据库连接、文件句柄、HTTP session\u003C\u002Fli>\n   \u003Cli>删除临时数据\u003C\u002Fli>\n   \u003Cli>恢复环境变量\u003C\u002Fli>\n   \u003Cli>停止容器或服务\u003C\u002Fli>\n   \u003Cli>回滚事务\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>小结：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>\u003Ccode>@pytest.fixture\u003C\u002Fcode> 负责\u003Cstrong>准备资源\u003C\u002Fstrong>；\u003Ccode>yield\u003C\u002Fcode> 之后的代码\u003Cstrong>负责收尾\u003C\u002Fstrong>。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>示例:\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.fixture\ndef service():\n    svc = start_service()\n    yield svc\n    svc.stop() # teardown 机制: `yield` 之后的代码负责【收尾】\n\n\ndef test_health(service):\n    assert service.healthcheck() is True\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>执行顺序：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Ccode>start_service()\u003C\u002Fcode>\u003C\u002Fli>\n \u003Cli>\u003Ccode>yield svc\u003C\u002Fcode>\u003C\u002Fli>\n \u003Cli>测试执行\u003C\u002Fli>\n \u003Cli>\u003Ccode>svc.stop()\u003C\u002Fcode> —— 测试后的后置动作 (teardown 特性)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>fixture 作用域（scope）\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.fixture(scope=\"module\")\ndef client():\n    return create_client()\n\ndef test_a(client):\n    ...\n\ndef test_b(client):\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>\u003Cstrong>同一个测试模块内，\u003Ccode>client\u003C\u002Fcode> 只会创建一次\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>参数化测试\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    [\n        (1, 1),\n        (-2, 2),\n        (0, 0),\n    ],\n)\ndef test_abs(value, expected):\n    assert abs(value) == expected\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>带 ID：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    [\n        pytest.param(1, 1, id=\"positive\"),\n        pytest.param(-2, 2, id=\"negative\"),\n    ],\n)\ndef test_abs(value, expected):\n    assert abs(value) == expected\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>临时目录\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>def test_write_config(tmp_path):\n    path = tmp_path \u002F \"config.yaml\"\n    path.write_text(\"debug: true\", encoding=\"utf-8\")\n\n    assert path.read_text(encoding=\"utf-8\") == \"debug: true\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>会话级临时目录：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.fixture(scope=\"session\")\ndef data_dir(tmp_path_factory):\n    return tmp_path_factory.mktemp(\"data\")\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Monkeypatch 环境变量\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import os\n\ndef test_app_mode(monkeypatch):\n    monkeypatch.setenv(\"APP_MODE\", \"test\")\n    assert os.environ[\"APP_MODE\"] == \"test\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Mock 属性或函数\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>from pathlib import Path\n\ndef get_ssh_path():\n    return Path.home() \u002F \".ssh\"\n\ndef test_get_ssh_path(monkeypatch):\n    monkeypatch.setattr(Path, \"home\", lambda: Path(\"\u002Fabc\"))\n    assert get_ssh_path() == Path(\"\u002Fabc\u002F.ssh\")\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>捕获 stdout\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>def test_stdout(capsys):\n    print(\"hello\")\n    captured = capsys.readouterr()\n    assert captured.out == \"hello\\n\"\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>近似比较\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import pytest\n\ndef test_float():\n    assert 0.1 + 0.2 == pytest.approx(0.3)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>\u003Ccode>conftest.py\u003C\u002Fcode> 共享 fixture\u003C\u002Fh3>\n\u003Cpre>\u003Ccode># tests\u002Fconftest.py\nimport pytest\n\n@pytest.fixture\ndef api_client():\n    client = create_test_client()\n    yield client\n    client.close()\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>在任意子目录测试中直接使用：\u003C\u002Fp>\n\u003Cpre>\u003Ccode># tests\u002Fapi\u002Ftest_users.py\ndef test_create_user(api_client):\n    response = api_client.post(\"\u002Fusers\", json={\"name\": \"Alice\"})\n    assert response.status_code == 201\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>覆盖率测试\u003C\u002Fh3>\n\u003Cp>通常配合 \u003Ccode>pytest-cov\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>python -m pip install pytest-cov\npytest --cov=your_package --cov-report=term-missing\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>并行执行\u003C\u002Fh3>\n\u003Cp>通常配合 \u003Ccode>pytest-xdist\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>python -m pip install pytest-xdist\npytest -n auto\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>失败快速停止\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>pytest -x\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>失败一批后停止：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pytest --maxfail=5\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>只查看会执行哪些测试\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>pytest --collect-only -q\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1173617\u002F202609\u002F1173617-20260910172536266-1821791280.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Ch3>推荐日常工作流 （必读）\u003C\u002Fh3>\n\u003Col>\n \u003Cli>先运行最窄范围：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>pytest tests\u002Ftest_that_file.py::test_target_case\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>再运行相关模块：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>pytest tests\u002Ftest_that_file.py\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>再运行整个相关包：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>pytest tests\u002Fapi\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>最后运行全量测试：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>pytest\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>需要优化速度时，查看耗时：\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>pytest --durations=10 --durations-min=0.1\n\u003C\u002Fcode>\u003C\u002Fpre>\nZ FAQ for pytest 框架\n\u003Ch2>Q: pytest 能否替代 unittest？\u003C\u002Fh2>\n\u003Cp>可以替代，但不一定要立刻替换。pytest 可以运行大量已有 \u003Ccode>unittest.TestCase\u003C\u002Fcode> 测试，因此推荐渐进迁移：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>新测试直接用 pytest 写。\u003C\u002Fli>\n \u003Cli>老测试先继续运行。\u003C\u002Fli>\n \u003Cli>需要重构时再逐步改成 fixture 风格。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Q: pytest 和 unittest 应该选哪个？推荐 pytest\u003C\u002Fh2>\n\u003Cp>多数 Python 项目更适合选 pytest：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>断言更简洁。\u003C\u002Fli>\n \u003Cli>fixture 更容易组合。\u003C\u002Fli>\n \u003Cli>参数化更强。\u003C\u002Fli>\n \u003Cli>插件生态更丰富。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>unittest\u003C\u002Fcode> 更适合：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>希望零第三方依赖。\u003C\u002Fli>\n \u003Cli>团队强依赖标准库。\u003C\u002Fli>\n \u003Cli>与某些旧工具链绑定较深。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Q: 为什么我的测试没有被收集？*（必读）\u003C\u002Fh2>\n\u003Cp>常见原因：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>文件名称\u003C\u002Fstrong>不是 \u003Ccode>test_*.py\u003C\u002Fcode> 或 \u003Ccode>*_test.py\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>函数名不是 \u003Ccode>test_*\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>类名不是 \u003Ccode>Test*\u003C\u002Fcode>，或类主动声明了 \u003Ccode>__init__\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>测试文件没有被导入，存在 import error。\u003C\u002Fli>\n \u003Cli>\u003Ccode>testpaths\u003C\u002Fcode> 配置没有覆盖目标目录。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>排查命令：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pytest --collect-only -q\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Q: fixture 和 setup\u002Fteardown 的区别是什么？\u003C\u002Fh2>\n\u003Cp>\u003Ccode>setup\u002Fteardown\u003C\u002Fcode> 是\u003Cstrong>过程式的生命周期钩子\u003C\u002Fstrong>；fixture 是\u003Cstrong>依赖注入式的资源管理机制\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>fixture 的优势：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>可以被多个测试复用。\u003C\u002Fli>\n \u003Cli>可以依赖其他 fixture。\u003C\u002Fli>\n \u003Cli>可以设置 scope。\u003C\u002Fli>\n \u003Cli>可以通过 \u003Ccode>yield\u003C\u002Fcode> 自动清理。\u003C\u002Fli>\n \u003Cli>可以参数化。\u003C\u002Fli>\n \u003Cli>可以在 \u003Ccode>conftest.py\u003C\u002Fcode> 中跨目录共享。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Q: 如何让测试之间互相独立？\u003C\u002Fh2>\n\u003Cp>建议：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>默认使用 \u003Ccode>function\u003C\u002Fcode> 级 fixture。\u003C\u002Fli>\n \u003Cli>避免共享可变对象。\u003C\u002Fli>\n \u003Cli>使用 \u003Ccode>tmp_path\u003C\u002Fcode> 管理临时文件。\u003C\u002Fli>\n \u003Cli>使用 \u003Ccode>monkeypatch\u003C\u002Fcode> 修改属性和环境变量。\u003C\u002Fli>\n \u003Cli>数据库测试使用事务回滚或独立数据快照。\u003C\u002Fli>\n \u003Cli>避免依赖测试执行顺序。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>Q: 如何跳过测试？（必读）\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>注解方式1\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import sys\nimport pytest\n\n@pytest.mark.skipif(sys.platform == \"win32\", reason=\"POSIX only\")\ndef test_posix_feature():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>注解方式2: 无条件跳过：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>@pytest.mark.skip(reason=\"temporary disabled\")\ndef test_not_ready():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>方式3: 运行时条件跳过：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest;\n\ndef test_feature():\n    if not feature_enabled():\n        pytest.skip(\"feature not enabled\")\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Q: 如何标记预期失败？（必读）\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>@pytest.mark.xfail(reason=\"known bug #123\", strict=True)\ndef test_known_bug():\n    ...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>参数化中的预期失败：\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>import pytest\n\n@pytest.mark.parametrize(\n    (\"value\", \"expected\"),\n    [\n        (1, 1),\n        pytest.param(-1, -1, id=\"known-bug\", marks=pytest.mark.xfail),\n    ],\n)\ndef test_normalize(value, expected):\n    assert abs(value) == expected\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Q: 如何测试异常和警告？\u003C\u002Fh2>\n\u003Ch3>异常\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import pytest\n\ndef parse_value(value):\n    if value is None:\n        raise ValueError(\"bad input\")\n    return int(value)\n\ndef test_bad_input():\n    with pytest.raises(ValueError, match=\"bad input\"):\n        parse_value(None)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>警告\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>import warnings\nimport pytest\n\ndef test_warning():\n    with pytest.warns(UserWarning, match=\"deprecated\"):\n        warnings.warn(\"deprecated API\", UserWarning)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Q: 如何并行运行？\u003C\u002Fh2>\n\u003Cp>核心包不内置完整并行能力，通常使用 \u003Ccode>pytest-xdist\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>python -m pip install pytest-xdist\npytest -n auto\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>前提是测试必须相互独立。对数据库、临时目录、端口等资源要做好隔离。\u003C\u002Fp>\n\u003Ch2>Q: pytest 的退出码代表什么？（必读）\u003C\u002Fh2>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>退出码\u003C\u002Fth>\n   \u003Cth>含义\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>0\u003C\u002Ftd>\n   \u003Ctd>全部通过\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>1\u003C\u002Ftd>\n   \u003Ctd>有测试失败\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>2\u003C\u002Ftd>\n   \u003Ctd>用户中断\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>3\u003C\u002Ftd>\n   \u003Ctd>pytest 内部错误\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>4\u003C\u002Ftd>\n   \u003Ctd>命令行或配置错误\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>5\u003C\u002Ftd>\n   \u003Ctd>没有收集到测试\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>6\u003C\u002Ftd>\n   \u003Ctd>超过最大警告数\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2>Q: Codex等AI Agent用pytest跑测试用例时，与真人用户用pytest跑测试用例时，默认会出现pytest文件目录的权限冲突(权限不足)问题 （必读）\u003C\u002Fh2>\n\u003Ch3>问题描述\u003C\u002Fh3>\n\u003Cul>\n \u003Cli>现象1\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>真人在基于\u003Ccode>pytest\u003C\u002Fcode>运行测试脚本时，报\u003Cstrong>权限不足\u003C\u002Fstrong>的错误。\u003Cbr>\n    备注：先用 codex 跑过 pytest 测试用例，后来真人用户又跑过测试用例。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>\\---\n(python-3.13) PS F:\\Codes-Minor\\Github\\ai-chatbot&gt; .\\\\.venv\\Scripts\\python -m pytest tests\u002Ftest\\_store.py -v\n\\=================================================================================== test session starts ====================================================================================\nplatform win32 -- Python 3.12.8, pytest-8.4.2, pluggy-1.6.0 -- F:\\Codes-Minor\\Github\\ai-chatbot\\\\.venv\\Scripts\\python.exe\ncachedir: .pytest\\_cache\nrootdir: F:\\Codes-Minor\\Github\\ai-chatbot\nplugins: anyio-4.15.1, langsmith-0.12.4\ncollected 3 items                                                                                                                                                                          &amp;#x20;\n\ntests\u002Ftest\\_store.py::test\\_embedding\\_is\\_deterministic\\_and\\_nonzero PASSED                                                                                                               [ 33%]\ntests\u002Ftest\\_store.py::test\\_persistent\\_store\\_round\\_trip ERROR                                                                                                                           [ 66%]\ntests\u002Ftest\\_store.py::test\\_history\\_and\\_knowledge\\_search ERROR                                                                                                                          [100%]\n...\n\\================================================================================= short test summary info ==================================================================================\nERROR tests\u002Ftest\\_store.py::test\\_persistent\\_store\\_round\\_trip - PermissionError: [WinError 5] 拒绝访问。: 'C:\\\\\\Users\\\\\\Johnny\\\\\\AppData\\\\\\Local\\\\\\Temp\\\\\\pytest-of-Johnny'\nERROR tests\u002Ftest\\_store.py::test\\_history\\_and\\_knowledge\\_search - PermissionError: [WinError 5] 拒绝访问。: 'C:\\\\\\Users\\\\\\Johnny\\\\\\AppData\\\\\\Local\\\\\\Temp\\\\\\pytest-of-Johnny'\n\\========================================================================= 1 passed, 2 warnings, 2 errors in 6.31s ==========================================================================\n(python-3.13) PS F:\\Codes-Minor\\Github\\ai-chatbot&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>问题原因\u003C\u002Fh3>\n\u003Cul>\n \u003Cli>现象1: 失败的两个测试使用了 \u003Ccode>tmp_path：F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py:15\u003C\u002Fcode> 和 \u003Ccode>F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py:41\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode># F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py\n...\n\ndef test_persistent_store_round_trip(tmp_path) -&gt; None: # 测试持久化存储(session \u002F message)的往返操作 | 第15行\n    settings = Settings(\n        chroma_dir=tmp_path \u002F \"chroma\",\n        session_collection=\"test_sessions\",\n        message_collection=\"test_messages\",\n        knowledge_collection=\"test_knowledge\",\n    )\n    store = ChatStore(settings=settings)\n    session = store.create_session() \u002F\u002F使用了本地存储\n\n    assert store.session_exists(session.session_id)\n    assert store.list_sessions() == [session]\n    assert store.is_available()\n\n    first = store.add_message(session.session_id, \"user\", \"What is this project?\")\n    second = store.add_message(session.session_id, \"assistant\", \"A local demo chat bot.\")\n\n    assert [record.message_id for record in store.get_messages(session.session_id)] == [\n        first.message_id,\n        second.message_id,\n    ]\n\n    reopened = ChatStore(settings=settings)\n    assert reopened.get_messages(session.session_id)[0].content == \"What is this project?\"\n\n...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>\u003Ccode>pytest\u003C\u002Fcode> 默认会使用 \u003Ccode>%TEMP%\\pytest-of-&lt;用户名&gt;\u003C\u002Fcode>，对应代码在 \u003Ccode>F:\\Codes-Minor\\Github\\ai-chatbot\\.venv\\Lib\\site-packages\\_pytest\\tmpdir.py:160\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode># mypy: allow-untyped-defs\n\"\"\"Support for providing temporary directories to test functions.\"\"\"\n\n...\n\nclass TempPathFactory:\n    def __init__(\n        self,\n        given_basetemp: Path | None,\n        retention_count: int,\n        retention_policy: RetentionType,\n        trace,\n        basetemp: Path | None = None, # 第60行 : basetemp\n        *,\n        _ispytest: bool = False,\n    ) -&gt; None:\n        check_ispytest(_ispytest)\n        ...\n\n    ...\n...\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n \u003Cli>经排查，检查到 \u003Ccode>C:\\Users\\Johnny\\AppData\\Local\\Temp\\pytest-of-Johnny\u003C\u002Fcode> 的 owner 是 \u003Ccode>LAPTOP-xxx\\CodexSandboxOffline\u003C\u002Fcode>，\u003Ccode>ACL\u003C\u002Fcode> 只允许 \u003Ccode>owner\u002FAdmin\u002FSYSTEM\u003C\u002Fcode> 访问；你真人用户的普通 PowerShell 是 \u003Ccode>Johnny\u003C\u002Fcode>，所以 \u003Ccode>pytest\u003C\u002Fcode> 读取\u002F清理该目录时报 \u003Ccode>WinError 5\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>\u003Ccode>ACL\u003C\u002Fcode> : Windows ACL （Access Control List，\u003Cstrong>访问控制列表\u003C\u002Fstrong>）是\u003Cstrong>用于控制对象访问权限和审计的安全机制\u003C\u002Fstrong>，包括 DACL 和 SACL 两种类型。Windows安全模型中的核心组件，用于定义用户或组对系统对象（如文件、目录、注册表项等）的访问权限和审计策略。每个ACL由多个ACE（Access Control Entry，访问控制项）组成，每个ACE指定一个用户或组的权限或审计规则。\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cul>\n \u003Cli>\u003Ccode>Codex\u003C\u002Fcode> \u003Cstrong>沙箱进程\u003C\u002Fstrong>里仍保留 \u003Ccode>USERNAME=Johnny\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>因此，虽然实际运行身份是 \u003Ccode>CodexSandboxOffline\u003C\u002Fcode>，它也会使用同一个 \u003Ccode>pytest-of-Johnny\u003C\u002Fcode>，导致2个环境共用并“抢占”这个目录。\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Ch3>解决方法\u003C\u002Fh3>\n\u003Cul>\n \u003Cli>\u003Cstrong>立即修复\u003C\u002Fstrong>：用管理员 PowerShell 执行 \u003Ccode>Remove-Item -LiteralPath \"$env:TEMP\\pytest-of-Johnny\" -Recurse -Force\u003C\u002Fcode>，然后重新运行测试。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>长期避免\u003C\u002Fstrong>：普通终端单独设置 Pytest \u003Cstrong>临时根\u003C\u002Fstrong>：\u003Ccode>$env:PYTEST_DEBUG_TEMPROOT = \"$env:TEMP\\pytest-johnny-terminal\"\u003C\u002Fcode>，再运行 \u003Ccode>.\\.venv\\Scripts\\python -m pytest tests\u002Ftest_store.py -v\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>或给不同环境使用不同 \u003Ccode>--basetemp\u003C\u002Fcode>，例如: 普通终端用 \u003Ccode>.pytest-tmp\\user\u003C\u002Fcode>，Codex 沙箱用 \u003Ccode>.pytest-tmp\\codex\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Chr>\n\u003Cul>\n \u003Cli>补充： powershell 下如何通过命令的方式查看指定目录的创建者账号、权限？\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cp>PowerShell 里通常能查到的是 \u003Cstrong>所有者 Owner\u003C\u002Fstrong>，Windows 不一定保存“\u003Cstrong>实际创建者\u003C\u002Fstrong>”（除非开启了审计\u002F事件记录）\u003C\u002Fp>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\n\u003Cpre>\u003Ccode>Get-Acl -LiteralPath F:\\Codes-Minor\\Github\\ai-chatbot | Select-Object Path,Owner,Access\n\n或: icacls \"F:\\Codes-Minor\\Github\\ai-chatbot\"\n\u003C\u002Fcode>\u003C\u002Fpre>\nY 推荐文献\n\u003Cul>\n \u003Cli>Pytest\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cblockquote>\n  \u003Cul>\n   \u003Cli>Github: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\u002F\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>官方文档: \u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>Getting started: \u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fgetting-started.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fgetting-started.html\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Fusage.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">How to invoke pytest - pytest\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Ffixtures.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">How to use fixtures - pytest\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Fparametrize.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">Parametrize tests - pytest\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Fmonkeypatch.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">Monkeypatch and environment - pytest\u003C\u002Fa>\u003C\u002Fli>\n   \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fhow-to\u002Fwriting_plugins.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">Writing plugins - pytest\u003C\u002Fa>\u003C\u002Fli>\n  \u003C\u002Ful>\n \u003C\u002Fblockquote>\n\u003C\u002Fblockquote>\nX 参考文献\n\u003Cul>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest GitHub Repository\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Official Documentation\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fpypi.org\u002Fproject\u002Fpytest\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest PyPI Page\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fchangelog.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Changelog\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fbackwards-compatibility.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Backwards Compatibility Policy\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Flatest\u002Freference\u002Fplugin_list.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Plugin List\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Freference\u002Fexit-codes.html\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Exit Codes\u003C\u002Fa>\u003C\u002Fli>\n \u003Cli>\u003Ca href=\"https:\u002F\u002Fapi.star-history.com\u002Fsvg?repos=pytest-dev\u002Fpytest&amp;type=Date\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">pytest Star History\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>","0 序 缘起: pytest 做单元测试 1 概述 产品介绍 定位：pytest 是一个面向 Python 的开源测试框架，主打“用最小代价写出可读性高的测试”，同时可扩展到复杂的功能测试、集成测试和端到端测试。 核心理念： 用普通 assert 表达断言，由 pytest 提供失败时的详细信息。 用 fixture(固定支具\u002F固定夹具) 管理测试前置条件和清理逻辑。 用 markers、parametrize、plugins 组织和扩展测试。 保持与 unittest 生态的兼容，允许渐进式迁移。 解决的核心问题： 降低测试代码编写成本。 减少传统 unittest 中大量的样板代码。 统一测试发现、参数化、跳过、失败报告和插件扩展机制。 URLs： 官网：https:\u002F\u002Fpytest.org 文档：https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F GitHub：https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest PyPI：https:\u002F\u002Fpypi.org\u002Fproject\u002Fpytest\u002F 当前项目状态 指标 数据 最新版本 9.1.1 最新版本发布时间 2026-06-19 支持的 Python 版本 Python 3.10+ License MIT GitHub Stars 14,493 GitHub Forks 3,354 Open Issues\u002FPRs 815 最近一次推送 2026-09-08 数据来源：GitHub API、PyPI API，查询时间为 2026-09-10。 发展历程 pytest 的历史较长，官方 README 中标注的项目版权起点为 2004 年，现代版本的公开发布记录可以从 1.0 之后清晰追溯。 时间 事件 2004 项目版权起点，早期与 py \u002F py.test 生态相关。 2009-08-04 发布 1.0.0，现代 pytest 版本线开始清晰化。 2010-11-25 发布 2.0.0。 2016-08-19 发布 3.0.0；官方文档提到该阶段引入了更清晰的弃用与兼容策略。 2018-11-14 发布 4.0.0。 2019-06-29 发布 5.0.0。 2020-07-28 发布 6.0.0。 2022-02-04 发布 7.0.0。 2024-01-27 发布 8.0.0，最低支持 Python 3.8。 2025-11-05 发布 9.0.0，最低支持提升到 Python 3.10。 2026-06-19 发布当前最新版本 9.1.1。 从长期演进看，pytest 的主线是： 保持测试写法简单。 强化 fixture 和插件体系。 逐步收紧兼容边界。 跟随 Python 版本演进。 维持庞大的第三方插件生态。 主要功能 自动测试发现 默认收集 test_*.py、*_test.py 文件。 默认收集 test_* 函数和 Test* 类。 增强断言 使用普通 assert。 失败时自动输出上下文。 支持 pytest.approx 做近似比较。 Fixture 依赖注入 测试通过函数参数声明依赖。 支持分层、复用、缓存和 teardown。 参数化测试 @pytest.mark.parametrize 可将一组输入展开成多个测试用例。 支持组合参数、pytest.param、id、marks。 Markers 支持内置 marker 和自定义 marker。 可用 -m 进行测试选择。 插件体系 内置插件。 conftest.py 本地插件。 第三方插件生态。 测试输出控制 -v、-q、-s、--tb=short 等。 支持 capsys、caplog、tmp_path 等内置 fixture。 跳过与预期失败 skip、skipif、xfail。 兼容 unittest 可以直接运行大量已有 unittest.TestCase 测试。 报告与调试 --durations 输出耗时。 --pdb 在失败处进入调试器。 核心优势 上手成本低 一个普通 Python 函数加一个 assert 就是一个测试。 断言体验好 不需要记忆大量 self.assertEqual 之类的 API。 fixture 系统强 相比传统 setup\u002Fteardown，fixture 更适合组合、复用和分层。 参数化能力强 一段测试逻辑可以自动展开成多组用例。 生态极大 官方 README 提到外部插件超过 1300 个。 常见方向包括覆盖率、并行执行、Django、asyncio、mock、数据库等。 与 unittest 兼容 老 项目可以渐进迁移，不必一次性重写。 社区成熟 长期维护、版本发布稳定、贡献者众多。 适合 CI\u002FCD 命令行接口稳定，易于接入 GitHub Actions、GitLab CI、Jenkins 等系统。 主要短板 【插件组合】可能带来维护成本 多个第三方插件同时修改收集、报告、fixture 行为时，可能产生兼容性问题。 【隐式行为】较多 fixture、autouse、conftest、marker 组合可能降低代码可读性。 复杂测试的【可读性】依赖团队约定 如果滥用高作用域 fixture，测试之间可能出现隐性共享状态。 【并行执行】并非完全内置 官方核心不内置完整并行调度，通常需要 pytest-xdist。 对【非常规测试场景】需要插件 例如 Web UI 测试、数据库快照、分布式环境等，通常要结合其他工具或插件。 大型测试套件收集【成本】可能上升 项目很大时，应控制 testpaths、导入路径和插件加载范围。 局限性 仅是【测试框架】，不是【完整的测试管理平台】 不内置测试计划、需求映射、用例评审等产品级功能。 不能解决所有【测试设计】问题 fixture 再强，也不能替代良好的测试分层和领域建模。 对 Python 版本有要求 当前 9.x 只支持 Python 3.10+。 非 Python 生态支持有限 虽然可以通过插件扩展，但核心优势仍在 Python 项目中。 内部 API 不适合【直接依赖】 第三方插件应尽量使用公开 API 和 hook，避免绑定内部实现细节。 适用场景 Python 库或应用的单元测试。 服务端 API 集成测试。 数据库访问层测试。 CLI 工具测试。 数据处理管道测试。 需要大量参数化场景的算法测试。 需要渐进迁移的 unittest 项目。 CI\u002FCD 中的自动化回归测试。 需要利用插件生态扩展覆盖率、并行、mock、日志等能力的项目。 不适合或需要谨慎使用的场景： 测试之间强依赖、共享大量全局状态。 希望只靠框架解决测试质量和测试分层问题。 期望一个工具同时承担测试管理、用例设计和缺陷跟踪。 同类竞品 竞品 定位 与 pytest 的关系 unittest Python 标准库测试框架 更稳定、零依赖，但样板代码更多；pytest 可运行大部分 unittest 测试。 nose2 unittest 扩展 曾经流行，但生态活跃度和插件生态弱于 pytest。 doctest 文档示例测试 适合校验文档示例，不适合作为完整测试框架。 Robot Framework 关键字驱动验收测试 更偏业务流程和 E2E，Python 编程能力不如 pytest 灵活。 Hypothesis 属性测试 可与 pytest 结合，属于互补关系。 tox \u002F nox 多环境管理 不是测试 runner 本身，常与 pytest 搭配使用。 自研测试框架 企业内部定制 官方生态通常更成熟，自研成本高。 发展趋势 社区活跃度高 截至 2026-09-10，最近一次推送为 2026-09-08。 2026 年内已发布多个版本，最新为 9.1.1。 Star \u002F Fork 规模大 GitHub Stars 当前为 14,493，Forks 为 3,354。 Star 趋势图：pytest Star History 生态继续向工程化扩展 覆盖率、并行、异步、数据库、Web、云环境等方向依赖插件生态快速发展。 兼容策略更清晰 官方维护了较明确的弃用、迁移和兼容政策。 总结 pytest 已经从“更好用的 unittest 替代品”发展成 Python 测试生态的事实标准，未来重点会继续放在兼容性、插件生态和大规模工程化上。 2 工作原理与架构 概念术语 术语 含义 test item 一条可执行的测试用例。 collector 负责发现和生成测试项的对象。 fixture 为测试提供前置状态、资源或环境的机制。 scope fixture 的生命周期范围。 marker 附加在测试上的元数据。 parametrize 把一组参数展开为多个测试用例。 conftest.py 目录级配置和本地插件文件。 plugin 扩展 pytest 行为的模块或包。 hook 插件介入 pytest 生命周期的切入点。 assertion rewriting pytest 通过 AST 重写增强普通 assert 的失败信息。 架构与运行原理 pytest 的核心流程可以概括为： 解析命令行参数和配置文件。 加载内置插件、第三方插件和 conftest.py。 构建【测试收集树】。 解析【测试依赖】，包括 fixture、marker、parametrize。 执行【测试用例】。 收集断言结果和输出。 执行 teardown。 生成报告、缓存和退出码。 结构示意： flowchart LR A[CLI \u002F config] --> B[Plugin manager] B --> C[Hooks] C --> D[Test collection tree] D --> E[Fixture resolution] E --> F[Run test item] F --> G[Assert \u002F report] F --> H[Teardown] G --> I[Cache \u002F exit code] 运行阶段 阶段 主要动作 启动 解析 pytest.ini、pyproject.toml、命令行参数。 插件加载 注册内置插件、外部插件和 conftest.py。 收集 根据命名规则和配置收集测试。 解析 分析 fixture、marker、parametrize。 执行 调用测试函数或测试类方法。 报告 汇总 pass、fail、skip、xfail、warning。 清理 逆序执行 fixture teardown。 退出 返回 0 到 6 的退出码。 fixture 生命周期 pytest 默认 fixture 作用域是 function。常见作用域包括： function：每个测试函数独立创建和销毁。 class：同一个测试类内共享。 module：同一个测试模块内共享。 package：同一个包内共享。 session：整个测试会话内共享。 3 使用指南 安装部署 Linux python3 -m pip install -U pytest python3 -m pytest --version Windows py -m pip install -U pytest py -m pytest --version 例如: (base) PS F:\\Codes-Minor\\Github\\ai-chatbot> .\\.venv\\Scripts\\python -m pytest --version pytest 8.4.2 如果不使用 py 启动器，也可以使用： python -m pip install -U pytest python -m pytest --version 推荐项目结构 (必读) project\u002F ├── src\u002F │ └── your_package\u002F ├── tests\u002F │ ├── conftest.py │ ├── test_unit.py │ └── test_integration.py ├── pyproject.toml └── README.md 常见配置 requirements-dev.txt 示例 -r requirements.txt ... pytest>=8.3.0,\u003C9 pyproject.toml 示例 [tool.pytest.ini_options] minversion = \"8.0\" addopts = \"-ra --strict-markers\" testpaths = [\"tests\"] markers = [ \"slow: marks tests as slow\", \"integration: requires external services\", ] 常用配置含义： testpaths：限制默认收集范围，提升速度。 addopts：为每次运行追加默认参数。 --strict-markers：未注册 marker 直接报错。 markers：显式注册自定义 marker，减少拼写错误。 常见用法 基本测试 (必读) # tests\u002Ftest_basic.py def inc(x): return x + 1 def test_answer(): assert inc(3) == 4 运行： pytest tests\u002Ftest_basic.py 或： python -m pytest tests\u002Ftest_basic.py 或 .\\.venv\\Scripts\\python -m pytest tests\u002Ftest_basic.py -v -v 参数 : --verbose 参数的缩写，表示 让 pytest 以更详细的方式输出测试结果。 不加 -v 时，输出通常比较简洁： tests\u002Ftest_basic.py . [100%] 加 -v 后，会显示完整的测试路径和测试名： tests\u002Ftest_basic.py::test_answer PASSED [100%] tests\u002Ftest_basic.py::test_eval PASSED [100%] tests\u002Ftest_basic.py::test_login FAILED [100%] 相关参数: -q\u002F-v\u002F-vv\u002F-s (必读) 参数 含义 -q quiet，简化输出；只看整体结果时用。 -v verbose，显示每个测试的完整 ID 和状态。 -vv 更详细；例如显示更完整的断言差异。 -s 显示测试里的 print() 输出。 -s 参数的示例 运行指定测试:文件\u002F函数\u002F类\u002F类方法 (必读) # 按文件\u002FFile pytest tests\u002Ftest_api.py # 按函数\u002FFunction pytest tests\u002Ftest_api.py::test_create_user # 按类\u002FClass pytest tests\u002Ftest_api.py::TestUsers # 按类方法\u002FClass::Method pytest tests\u002Ftest_api.py::TestUsers::test_create_user 按表达式筛选(-k) pytest -k \"login and not slow\" # 注: 运行测试 ID 中包含 login 的测试，但排除名称中包含 slow 的测试。这里的“【名称】”包括：文件名、类名、函数名。 pytest -k \"TestUsers or test_create\" pytest -k \"not integration\" 注意：-k 参数匹配的是“测试名称”里的关键词，不是 marker。 -k vs -m(marker) 参数 匹配对象 -k 文件名、类名、函数名中的关键词 -m @pytest.mark.xxx 定义的 marker 按 marker 筛选 定义、使用 Marker 是 pytest 里的“测试标签\u002F元数据”机制。 核心用途：给测试打标记，然后可以按标记筛选、跳过、归类，或让插件识别这些标签并做出特殊处理。 例如: import pytest @pytest.mark.slow def test_login(): ... 这句话的意思不是“这个测试会自动变慢”，而是：给 test_login 这个测试用例打上一个名为 slow 的标签。 之后可以在命令行按 marker 筛选： pytest -m slow # 只运行带 slow 标记的测试 pytest -m \"not slow\" # 运行所有不带 slow 标记的测试 pytest -m \"live and not slow\" 2种写法 写法1: pytest.mark.slow：标记单个测试 import pytest @pytest.mark.slow def test_login(): ... 含义： test_login 这个测试被标记为 slow。 运行： pytest -m slow 就会选中它。 写法2：pytestmark = pytest.mark.live：标记整个模块 import pytest pytestmark = pytest.mark.live def test_login(): ... def test_logout(): ... 含义：当前这个测试模块里的所有测试，都会被标记为 live。 等价于给模块里的每个测试都打上 live 标签： @pytest.mark.live def test_login(): ... @pytest.mark.live def test_logout(): ... live marker = 通常是团队自定义的 marker 通常 live 是团队自定义的 marker，常见含义是： 需要真实外部环境； 会访问真实网络或真实数据库； 运行成本高、可能有副作用； 【不适合】默认在单元测试中跑。 例如： # 默认不跑需要真实环境的测试。 pytest -m \"not live\" 内置 marker vs 自定义 marker pytest 自带一些有特殊行为的 marker，例如： Marker 含义 pytest.mark.skip 无条件跳过 pytest.mark.skipif 条件跳过 pytest.mark.xfail 预期失败 pytest.mark.parametrize 参数化测试 pytest.mark.usefixtures 显式使用 fixture pytest.mark.filterwarnings 过滤警告 而 live、slow、serial 通常是项目自定义标签。它们默认只是元数据，不会自动改变测试行为；需要配合 -m、插件或 hook 使用。 建议在配置里注册，例如： [tool.pytest.ini_options] markers = [ \"live: tests that require a live external environment\", \"slow: tests that are slow to run\", ] 注册后，pytest 能识别这些 marker，避免“未知 marker”警告。 常用运行参数 （必读） 命令 作用 pytest -v 显示更详细的测试名。 pytest -q 安静模式。 pytest -s 禁用输出捕获，直接显示 print。 pytest -x 首个失败后停止。 pytest --maxfail=3 失败 3 次后停止。 pytest --tb=short 简短 traceback。 pytest --tb=long 详细 traceback。 pytest --durations=10 显示最慢的 10 个测试。 pytest --pdb 失败后进入调试器。 pytest --collect-only -q 只收集不执行。 pytest --fixtures 查看可用 fixture。 pytest --markers 查看可用 marker。 基础 fixture (依赖注入机制) —— 提前准备数据与资源 @pytest.fixture 是 pytest 的 依赖注入机制，用来为测试准备它们需要的资源或状态。 通俗理解：@pytest.fixture 就是把“测试的前置准备”变成可复用的依赖；测试只要写参数名，pytest 负责准备和注入。 核心规则： 测试函数声明什么参数名，pytest 就自动找同名 fixture，执行它并把返回值注入进来。例如： import pytest @pytest.fixture def user(): return {\"id\": 1, \"name\": \"Alice\"} def test_user_name(user): assert user[\"name\"] == \"Alice\" 这里的 user 不是普通参数，而是由 fixture 机制提供的测试数据。 常见用途： 构造测试对象 创建数据库\u002FHTTP 客户端 准备临时目录 初始化日志、配置 在测试后清理资源 例如: @pytest.fixture def client(): app = create_app() with app.test_client() as c: yield c fixture teardown（测试后清理机制） fixture teardown 是 pytest 的 测试后清理机制，用于在测试完成后释放资源、恢复状态。 tear down : 英译: 拆卸、销毁 核心写法： import pytest @pytest.fixture def db(): conn = connect_db() yield conn # 交给测试使用 conn.close() # teardown 机制：测试结束后执行 执行顺序： 执行 fixture 前半段，创建资源。 yield conn 把资源注入测试。 测试运行。 执行 yield 后面的清理代码。 常见清理内容： 关闭数据库连接、文件句柄、HTTP session 删除临时数据 恢复环境变量 停止容器或服务 回滚事务 小结： @pytest.fixture 负责准备资源；yield 之后的代码负责收尾。 示例: import pytest @pytest.fixture def service(): svc = start_service() yield svc svc.stop() # teardown 机制: `yield` 之后的代码负责【收尾】 def test_health(service): assert service.healthcheck() is True 执行顺序： start_service() yield svc 测试执行 svc.stop() —— 测试后的后置动作 (teardown 特性) fixture 作用域（scope） import pytest @pytest.fixture(scope=\"module\") def client(): return create_client() def test_a(client): ... def test_b(client): ... 同一个测试模块内，client 只会创建一次。 参数化测试 import pytest @pytest.mark.parametrize( (\"value\", \"expected\"), [ (1, 1), (-2, 2), (0, 0), ], ) def test_abs(value, expected): assert abs(value) == expected 带 ID： @pytest.mark.parametrize( (\"value\", \"expected\"), [ pytest.param(1, 1, id=\"positive\"), pytest.param(-2, 2, id=\"negative\"), ], ) def test_abs(value, expected): assert abs(value) == expected 临时目录 def test_write_config(tmp_path): path = tmp_path \u002F \"config.yaml\" path.write_text(\"debug: true\", encoding=\"utf-8\") assert path.read_text(encoding=\"utf-8\") == \"debug: true\" 会话级临时目录： import pytest @pytest.fixture(scope=\"session\") def data_dir(tmp_path_factory): return tmp_path_factory.mktemp(\"data\") Monkeypatch 环境变量 import os def test_app_mode(monkeypatch): monkeypatch.setenv(\"APP_MODE\", \"test\") assert os.environ[\"APP_MODE\"] == \"test\" Mock 属性或函数 from pathlib import Path def get_ssh_path(): return Path.home() \u002F \".ssh\" def test_get_ssh_path(monkeypatch): monkeypatch.setattr(Path, \"home\", lambda: Path(\"\u002Fabc\")) assert get_ssh_path() == Path(\"\u002Fabc\u002F.ssh\") 捕获 stdout def test_stdout(capsys): print(\"hello\") captured = capsys.readouterr() assert captured.out == \"hello\\n\" 近似比较 import pytest def test_float(): assert 0.1 + 0.2 == pytest.approx(0.3) conftest.py 共享 fixture # tests\u002Fconftest.py import pytest @pytest.fixture def api_client(): client = create_test_client() yield client client.close() 在任意子目录测试中直接使用： # tests\u002Fapi\u002Ftest_users.py def test_create_user(api_client): response = api_client.post(\"\u002Fusers\", json={\"name\": \"Alice\"}) assert response.status_code == 201 覆盖率测试 通常配合 pytest-cov： python -m pip install pytest-cov pytest --cov=your_package --cov-report=term-missing 并行执行 通常配合 pytest-xdist： python -m pip install pytest-xdist pytest -n auto 失败快速停止 pytest -x 失败一批后停止： pytest --maxfail=5 只查看会执行哪些测试 pytest --collect-only -q 推荐日常工作流 （必读） 先运行最窄范围： pytest tests\u002Ftest_that_file.py::test_target_case 再运行相关模块： pytest tests\u002Ftest_that_file.py 再运行整个相关包： pytest tests\u002Fapi 最后运行全量测试： pytest 需要优化速度时，查看耗时： pytest --durations=10 --durations-min=0.1 Z FAQ for pytest 框架 Q: pytest 能否替代 unittest？ 可以替代，但不一定要立刻替换。pytest 可以运行大量已有 unittest.TestCase 测试，因此推荐渐进迁移： 新测试直接用 pytest 写。 老测试先继续运行。 需要重构时再逐步改成 fixture 风格。 Q: pytest 和 unittest 应该选哪个？推荐 pytest 多数 Python 项目更适合选 pytest： 断言更简洁。 fixture 更容易组合。 参数化更强。 插件生态更丰富。 unittest 更适合： 希望零第三方依赖。 团队强依赖标准库。 与某些旧工具链绑定较深。 Q: 为什么我的测试没有被收集？*（必读） 常见原因： 文件名称不是 test_*.py 或 *_test.py。 函数名不是 test_*。 类名不是 Test*，或类主动声明了 __init__。 测试文件没有被导入，存在 import error。 testpaths 配置没有覆盖目标目录。 排查命令： pytest --collect-only -q Q: fixture 和 setup\u002Fteardown 的区别是什么？ setup\u002Fteardown 是过程式的生命周期钩子；fixture 是依赖注入式的资源管理机制。 fixture 的优势： 可以被多个测试复用。 可以依赖其他 fixture。 可以设置 scope。 可以通过 yield 自动清理。 可以参数化。 可以在 conftest.py 中跨目录共享。 Q: 如何让测试之间互相独立？ 建议： 默认使用 function 级 fixture。 避免共享可变对象。 使用 tmp_path 管理临时文件。 使用 monkeypatch 修改属性和环境变量。 数据库测试使用事务回滚或独立数据快照。 避免依赖测试执行顺序。 Q: 如何跳过测试？（必读） 注解方式1 import sys import pytest @pytest.mark.skipif(sys.platform == \"win32\", reason=\"POSIX only\") def test_posix_feature(): ... 注解方式2: 无条件跳过： @pytest.mark.skip(reason=\"temporary disabled\") def test_not_ready(): ... 方式3: 运行时条件跳过： import pytest; def test_feature(): if not feature_enabled(): pytest.skip(\"feature not enabled\") Q: 如何标记预期失败？（必读） @pytest.mark.xfail(reason=\"known bug #123\", strict=True) def test_known_bug(): ... 参数化中的预期失败： import pytest @pytest.mark.parametrize( (\"value\", \"expected\"), [ (1, 1), pytest.param(-1, -1, id=\"known-bug\", marks=pytest.mark.xfail), ], ) def test_normalize(value, expected): assert abs(value) == expected Q: 如何测试异常和警告？ 异常 import pytest def parse_value(value): if value is None: raise ValueError(\"bad input\") return int(value) def test_bad_input(): with pytest.raises(ValueError, match=\"bad input\"): parse_value(None) 警告 import warnings import pytest def test_warning(): with pytest.warns(UserWarning, match=\"deprecated\"): warnings.warn(\"deprecated API\", UserWarning) Q: 如何并行运行？ 核心包不内置完整并行能力，通常使用 pytest-xdist： python -m pip install pytest-xdist pytest -n auto 前提是测试必须相互独立。对数据库、临时目录、端口等资源要做好隔离。 Q: pytest 的退出码代表什么？（必读） 退出码 含义 0 全部通过 1 有测试失败 2 用户中断 3 pytest 内部错误 4 命令行或配置错误 5 没有收集到测试 6 超过最大警告数 Q: Codex等AI Agent用pytest跑测试用例时，与真人用户用pytest跑测试用例时，默认会出现pytest文件目录的权限冲突(权限不足)问题 （必读） 问题描述 现象1 真人在基于pytest运行测试脚本时，报权限不足的错误。 备注：先用 codex 跑过 pytest 测试用例，后来真人用户又跑过测试用例。 \\--- (python-3.13) PS F:\\Codes-Minor\\Github\\ai-chatbot> .\\\\.venv\\Scripts\\python -m pytest tests\u002Ftest\\_store.py -v \\=================================================================================== test session starts ==================================================================================== platform win32 -- Python 3.12.8, pytest-8.4.2, pluggy-1.6.0 -- F:\\Codes-Minor\\Github\\ai-chatbot\\\\.venv\\Scripts\\python.exe cachedir: .pytest\\_cache rootdir: F:\\Codes-Minor\\Github\\ai-chatbot plugins: anyio-4.15.1, langsmith-0.12.4 collected 3 items &#x20; tests\u002Ftest\\_store.py::test\\_embedding\\_is\\_deterministic\\_and\\_nonzero PASSED [ 33%] tests\u002Ftest\\_store.py::test\\_persistent\\_store\\_round\\_trip ERROR [ 66%] tests\u002Ftest\\_store.py::test\\_history\\_and\\_knowledge\\_search ERROR [100%] ... \\================================================================================= short test summary info ================================================================================== ERROR tests\u002Ftest\\_store.py::test\\_persistent\\_store\\_round\\_trip - PermissionError: [WinError 5] 拒绝访问。: 'C:\\\\\\Users\\\\\\Johnny\\\\\\AppData\\\\\\Local\\\\\\Temp\\\\\\pytest-of-Johnny' ERROR tests\u002Ftest\\_store.py::test\\_history\\_and\\_knowledge\\_search - PermissionError: [WinError 5] 拒绝访问。: 'C:\\\\\\Users\\\\\\Johnny\\\\\\AppData\\\\\\Local\\\\\\Temp\\\\\\pytest-of-Johnny' \\========================================================================= 1 passed, 2 warnings, 2 errors in 6.31s ========================================================================== (python-3.13) PS F:\\Codes-Minor\\Github\\ai-chatbot> 问题原因 现象1: 失败的两个测试使用了 tmp_path：F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py:15 和 F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py:41。 # F:\\Codes-Minor\\Github\\ai-chatbot\\tests\\test_store.py ... def test_persistent_store_round_trip(tmp_path) -> None: # 测试持久化存储(session \u002F message)的往返操作 | 第15行 settings = Settings( chroma_dir=tmp_path \u002F \"chroma\", session_collection=\"test_sessions\", message_collection=\"test_messages\", knowledge_collection=\"test_knowledge\", ) store = ChatStore(settings=settings) session = store.create_session() \u002F\u002F使用了本地存储 assert store.session_exists(session.session_id) assert store.list_sessions() == [session] assert store.is_available() first = store.add_message(session.session_id, \"user\", \"What is this project?\") second = store.add_message(session.session_id, \"assistant\", \"A local demo chat bot.\") assert [record.message_id for record in store.get_messages(session.session_id)] == [ first.message_id, second.message_id, ] reopened = ChatStore(settings=settings) assert reopened.get_messages(session.session_id)[0].content == \"What is this project?\" ... pytest 默认会使用 %TEMP%\\pytest-of-\u003C用户名>，对应代码在 F:\\Codes-Minor\\Github\\ai-chatbot\\.venv\\Lib\\site-packages\\_pytest\\tmpdir.py:160。 # mypy: allow-untyped-defs \"\"\"Support for providing temporary directories to test functions.\"\"\" ... class TempPathFactory: def __init__( self, given_basetemp: Path | None, retention_count: int, retention_policy: RetentionType, trace, basetemp: Path | None = None, # 第60行 : basetemp *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) ... ... ... 经排查，检查到 C:\\Users\\Johnny\\AppData\\Local\\Temp\\pytest-of-Johnny 的 owner 是 LAPTOP-xxx\\CodexSandboxOffline，ACL 只允许 owner\u002FAdmin\u002FSYSTEM 访问；你真人用户的普通 PowerShell 是 Johnny，所以 pytest 读取\u002F清理该目录时报 WinError 5。 ACL : Windows ACL （Access Control List，访问控制列表）是用于控制对象访问权限和审计的安全机制，包括 DACL 和 SACL 两种类型。Windows安全模型中的核心组件，用于定义用户或组对系统对象（如文件、目录、注册表项等）的访问权限和审计策略。每个ACL由多个ACE（Access Control Entry，访问控制项）组成，每个ACE指定一个用户或组的权限或审计规则。 Codex 沙箱进程里仍保留 USERNAME=Johnny。 因此，虽然实际运行身份是 CodexSandboxOffline，它也会使用同一个 pytest-of-Johnny，导致2个环境共用并“抢占”这个目录。 解决方法 立即修复：用管理员 PowerShell 执行 Remove-Item -LiteralPath \"$env:TEMP\\pytest-of-Johnny\" -Recurse -Force，然后重新运行测试。 长期避免：普通终端单独设置 Pytest 临时根：$env:PYTEST_DEBUG_TEMPROOT = \"$env:TEMP\\pytest-johnny-terminal\"，再运行 .\\.venv\\Scripts\\python -m pytest tests\u002Ftest_store.py -v。 或给不同环境使用不同 --basetemp，例如: 普通终端用 .pytest-tmp\\user，Codex 沙箱用 .pytest-tmp\\codex。 补充： powershell 下如何通过命令的方式查看指定目录的创建者账号、权限？ PowerShell 里通常能查到的是 所有者 Owner，Windows 不一定保存“实际创建者”（除非开启了审计\u002F事件记录） Get-Acl -LiteralPath F:\\Codes-Minor\\Github\\ai-chatbot | Select-Object Path,Owner,Access 或: icacls \"F:\\Codes-Minor\\Github\\ai-chatbot\" Y 推荐文献 Pytest Github: https:\u002F\u002Fgithub.com\u002Fpytest-dev\u002Fpytest\u002F 官方文档: https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002F Getting started: https:\u002F\u002Fdocs.pytest.org\u002Fen\u002Fstable\u002Fgetting-started.html How to invoke pytest - pytest How to use fixtures - pytest Parametrize tests - pytest Monkeypatch and environment - pytest Writing plugins - pytest X 参考文献 pytest GitHub Repository pytest Official Documentation pytest PyPI Page pytest Changelog pytest Backwards Compatibility Policy pytest Plugin List pytest Exit Codes pytest Star History",16705,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":16,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":38},"#2563eb","16 \u002F 10",[7,8],{"targetType":8,"targetId":9,"likedByMe":40,"likeCount":41,"commentCount":41,"contentLikeCount":41,"contentCommentCount":41,"sourceLikeCount":41,"sourceCommentCount":41},false,0,[43,52,58,64,73,80,86,93],{"id":44,"kind":7,"title":45,"summary":46,"image":47,"href":48,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":50},"NEWS_ARTICLE:927","HelloCrab - 短视频开源爬虫，仅供学习参考","HelloCrab 基于 Avalonia、Playwright、AI与 FFmpeg 的跨平台桌面采集器，支持9大平台，以及 Android、iOS、Browser 远程控制端。 Made By ChatGPT &amp; Vincent with ❤ 平台 是否接入 哔哩哔哩 ✅ 抖音 ✅ 快手","https:\u002F\u002Fwww.cnblogs.com\u002Fhupo376787\u002Fp\u002FScreenshot\u002FWindows.jpg","\u002Fnews\u002F927","2026 · 软件开发",[51],"软件开发",{"id":53,"kind":7,"title":54,"summary":55,"image":15,"href":56,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":57},"NEWS_ARTICLE:929",".NET 异常处理的\"暗门\"：代码里写满 catch，你依然能抓住它——从一个 AI Agent 运行时的源码说起","一个健壮的系统，必然到处都是有意的 catch；异常被消化不等于问题不存在。 观测与韧性，是一个硬币的两面——降级逻辑保证系统不崩，FirstChance 保证你能看见它为什么降级。","\u002Fnews\u002F929",[51],{"id":59,"kind":7,"title":60,"summary":61,"image":15,"href":62,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":63},"NEWS_ARTICLE:928","架构师化繁为简，执行者化简为繁","新手改三天，你改三行——反而是你显得更不重要。因为化繁为简做得越纯熟，产出看起来越小。这篇聊聊两种能力的辩证关系，以及为什么'看不见'的那部分工作，恰恰是最难的部分。","\u002Fnews\u002F928",[7,8],{"id":65,"kind":7,"title":66,"summary":67,"image":68,"href":69,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":71},"NEWS_ARTICLE:930","基于 vLLM+Nginx 构建负载均衡推理集群","企业内部私有环境部署大模型推理集群时，很容易遇到流量调度混乱、节点负载失衡、会话上下文丢失、接口缺少鉴权防护等一系列问题，单 vLLM 推理节点难以支撑并发请求。本文基于 Ubuntu 22.04 系统环境，搭建 Nginx + vLLM-Semantic-Router + vLLM-Router","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1379525\u002F202609\u002F1379525-20260910165534385-2022408098.png","\u002Fnews\u002F930","2026 · 人工智能",[72],"人工智能",{"id":74,"kind":7,"title":75,"summary":76,"image":77,"href":78,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":79},"NEWS_ARTICLE:932","2026年AI编程工具大全，33个主流工具一次看懂","事情是这样的，前两天看到一张图，是某个社区官网的「支持的工具」清单，我数了数，整整31个AI编程工具。 两年前这份清单撑死5个，现在直接31个，而且我居然每一个都认识。。。 干脆整理成一篇，顺手把最近字节的TraeWork和豆包工作也补了进来，凑成33个。 今天给大家推荐一遍，每个工具说说它是干什么","https:\u002F\u002Fimage.kjdaohang.com\u002Fimg\u002F20260909210838518.png","\u002Fnews\u002F932",[72],{"id":81,"kind":7,"title":82,"summary":83,"image":15,"href":84,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":85},"NEWS_ARTICLE:931","SH 中文化样例数据使用手册","在数据库演示与 PoC 场景中，Oracle 自带的 SH 示例模式虽然经典，但英文维度数据往往让国内演示效果打折扣。笔者整理了一套方案：保留 SH 标准英文对象名，同时装载中文化维度数据，并按参数生成可复现的销售历史数据，方便个人测试与概念验证。 01 | 环境准备 使用前请确认满足以下条件： O","\u002Fnews\u002F931",[7,8],{"id":87,"kind":7,"title":88,"summary":89,"image":90,"href":91,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":92},"NEWS_ARTICLE:933","写给 C++ 工程师的 OpenClaw.NET 上手指南：用你熟悉的 C++ 思维，跑起一个生产级 AI Agent","它像一个「基于 boost.asio + REST 端点的常驻服务」——对外是 HTTP \u002F WebSocket \u002F 各 IM 的 webhook，对内跑着一个能调工具、读写记忆、跨渠道对话的 AI Agent——只不过协程不用你手写 promise_type，内存不用你管 new\u002Fdelete。","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F510\u002F202609\u002F510-20260905070811424-1837144874.jpg","\u002Fnews\u002F933",[51],{"id":94,"kind":7,"title":95,"summary":96,"image":97,"href":98,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":99},"NEWS_ARTICLE:935","[Agent Memory \u002F 强化学习] MemPO源码学习笔记 ---（1）--- 总体","[Agent Memory \u002F 强化学习] MemPO源码学习笔记 （1） 总体 目录[Agent Memory \u002F 强化学习] MemPO源码学习笔记 （1） 总体0x00 概要0x01 基础 &amp; 背景1.1 用RL训练记忆系统的要点1.2 主要难点1.3 主要思路1.4 RL训练方案1.","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1850883\u002F202609\u002F1850883-20260906190854637-986949885.jpg","\u002Fnews\u002F935",[72]]