尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Pycharm集成pytest完整指南:从环境配置到测试运行

Pycharm集成pytest完整指南:从环境配置到测试运行 Pycharm部署pytest运行测试测试笔记一写这篇笔记的起因很简单很多刚开始接触pytest的同学上来就在终端里跑pytest命令跑通了就觉得自己已经会了。但真到了项目里、团队里大部分时间你是在IDE里写用例、点按钮、看报告、跟同事互相review测试代码。Pycharm作为Python社区最常用的IDE它对pytest的集成其实非常深只是好多人没把这层配置吃透导致“命令行能跑、Pycharm里跑不起来”的怪问题反复出现。这篇文章就是把我自己从零开始在Pycharm里部署pytest、写用例、跑测试、看报告、排查问题的一套完整流程记录下来。它不是什么高深理论就是一个测试开发每天都要做的基础操作。标题里说的“部署”准确理解不是服务器部署而是把pytest这个测试框架和Pycharm开发环境完整地集成起来做到选中一个用例就能跑、打断点就能调试、失败了一键跳源码。如果你正准备从unittest切到pytest或者刚进项目发现同事都用Pycharm跑测试而你还停留在命令行那这篇笔记应该能帮你省不少时间。1. 部署前准备与初始环境搭建1.1 Python环境与Pycharm版本选择先说一下基础环境。pytest本身对Python版本的要求是3.8及以上pytest 7.x系列目前最新的pytest 8.x也完全支持3.8以上的语法特性。如果你还在用Python 2.7或者3.6那就别折腾了先升级Python版本再说。我自己的主力环境是Python 3.10和3.11两个版本跑pytest都没有任何兼容问题。Pycharm分社区版Community和专业版Professional。社区版免费日常写Python、跑pytest完全够用。专业版多了数据库工具、远程开发、Django/Flask的Web框架支持等对纯测试工作来说不是必需的。我的建议是如果你只是做Python接口自动化、单元测试社区版就很好没必要一开始就上专业版。但如果你要在同一个IDE里连数据库查测试数据、或者做Web自动化相关的调试专业版会更顺手。安装Pycharm本身没什么难度官网下载对应系统的安装包一路下一步就行。这里提醒一句网上那些“破解版”“激活码”的资源千万不要碰一个是安全风险另一个是社区版真的够用没必要为了用不到的功能去冒这个险。1.2 安装pytest与常用插件环境准备好之后第一步是在Python环境里安装pytest。如果你创建项目时用的是虚拟环境强烈建议需要先激活虚拟环境再安装否则包会装到全局环境里项目一换机器就歇菜。安装命令很简单pip install pytest装完验证一下版本确认安装成功并且能看到当前解释器的路径pytest --version输出类似这样pytest 8.2.0 Python 3.10.12 (tags/v3.10.12:...)看到这个输出说明pytest已经能正常识别当前Python环境了。这行输出很重要它同时告诉你两件事pytest的版本、它绑定的Python版本。如果这行输出里的Python路径不是你在Pycharm里配置的虚拟环境路径那就要小心了后面大概率会出现“终端能跑、Pycharm跑不了”的问题。除了pytest本体我建议把下面几个插件一起装上都是测试工作里高频使用的后面章节会讲具体用法pip install pytest-html pytest-cov pytest-xdist pytest-rerunfailures简单说下各自用途pytest-html用于生成HTML测试报告pytest-cov用于统计代码覆盖率pytest-xdist用来多进程并行跑用例加快执行速度pytest-rerunfailures可以对失败的用例做重试适合处理网络不稳定导致的偶发失败。还有一个细节建议把项目依赖记录到requirements.txt里方便其他人一键复现环境pip freeze requirements.txt这样别人拿到项目后只需要pip install -r requirements.txt就能把环境装齐。团队协作时这个文件比口头说“你装一下pytest”要靠谱得多。1.3 虚拟环境的作用与配置为什么要强调虚拟环境我用一个例子说明假设你同时做两个项目项目A用pytest 7.x项目B用pytest 8.x如果都装在全局环境里必然互相打架。虚拟环境把每个项目的依赖隔离在自己的目录里互不干扰。这就像每个项目有自己的工具箱里面的工具版本可以不一样而不是所有人共用一个工具间。在Pycharm里创建新项目时它会默认帮你创建venv虚拟环境。如果你已经有项目了也可以通过File - Settings - Project - Python Interpreter看到当前项目用的是哪个解释器路径里通常会有venv或.venv字样说明正在使用虚拟环境。如果没有点击Add Interpreter - Add Local Interpreter选择Virtualenv Environment新建一个。Pycharm创建虚拟环境时有个选项叫“Inherit global site-packages”这个复选框我建议勾掉。一旦勾上虚拟环境就能看到全局环境里的所有包看起来方便但实际上破坏了隔离性容易出现“我这个项目里为什么会多出这个包”的困惑。保持纯净的虚拟环境依赖全部从requirements.txt装这才是可复现的环境。2. Pycharm中集成pytest的核心设置2.1 将默认测试运行器切换为pytest这是整个部署过程里最关键的一步我见过太多人卡在这一步。Pycharm默认的测试运行器不一定是pytest它可能是unittest。如果不切换你在测试文件里写assert 1 1然后右键运行会发现提示No tests were found或者它用unittest的方式去跑结果一堆莫名其妙的报错。切换路径File - Settings - Tools - Python Integrated Tools - Default test runner把下拉框从unittest改成pytest点击OK保存。设置完成后Pycharm对测试文件的识别逻辑就会切换成pytest的发现规则。这个设置是全局的对所有项目生效。我推荐在每个项目落地时都先检查这一项不要等跑不起来了再回头找原因。2.2 配置项目解释器与工作目录切换完默认运行器后还要确认两件事项目解释器、工作目录。项目解释器的位置在File - Settings - Project - Python Interpreter确认这里的解释器路径是你在终端里激活的那个虚拟环境里的Python。怎么验证在Pycharm底部的Terminal里执行python --version再对比Settings里的解释器路径两者一致就说明没问题。工作目录的配置藏得稍微深一点。右键测试文件选择Edit pytest in test_demo.py...不同版本菜单名称略有差异在弹出的Run Configuration对话框里能看到Working directory这一项。这个目录决定了用例运行时以哪个路径为根目录。举个例子如果项目里导入了utils这个模块且utils目录在项目根目录下那Working directory必须是项目根目录否则Python找不到utils模块直接报ModuleNotFoundError。一般新建项目时Pycharm会把Working directory默认设置为项目根目录但如果你用旧版本打开过、或者配置项被改过就会出问题。这时候手动改回项目根目录就行。2.3 认识pytest的测试发现规则pytest能自动识别哪些文件是测试文件、哪些函数是测试用例靠的是一套约定规则。默认规则如下文件名test_*.py或*_test.py比如test_login.py、api_test.py类名以Test开头且类中不能有__init__方法函数名/方法名以test开头比如test_login_success这条规则是理解一切“为什么我的用例没被发现”问题的基础。很多新手在文件命名上踩坑比如把测试文件叫做login_check.py或者把测试函数命名为check_login()在Pycharm里点右键会发现根本没有Run pytest这个选项或者运行后显示collected 0 items原因就是命名不符合规则。如果怀疑用例没被收集到最快的方式是在终端里执行pytest --collect-only -v这个命令会把所有收集到的用例列出来一目了然。比如输出tests/test_login.py::test_login_success说明这个用例被正确识别了。如果输出为空那就去检查命名规则和目录配置。3. 编写第一个pytest用例并跑通闭环3.1 初始化项目目录结构部署的最终目的不是装好环境而是能顺畅地写用例、跑用例。我建议在项目里建立这样一个基础目录结构project_root/ ├── app/ # 业务代码目录 │ ├── __init__.py │ └── login.py ├── tests/ # 测试代码目录 │ ├── __init__.py │ ├── conftest.py # 共享fixture配置 │ └── test_login.py ├── pytest.ini # pytest配置文件 ├── requirements.txt └── venv/这个结构的好处是业务代码和测试代码分离互不污染pytest.ini里可以通过testpaths指定只扫描tests目录避免pytest把业务代码里不符合命名规则的文件也扫描一遍conftest.py天然就是Pycharm能识别的pytest配置载体放fixture不需要在测试文件里import。pytest.ini虽然以后会在第6部分详细讲配置项但先放一个最基础版本保证跑通[pytest] testpaths tests python_files test_*.py python_functions test_*3.2 从unittest风格到pytest断言的转变很多从unittest切到pytest的人最难适应的是断言方式的变化。unittest要求你记一堆断言方法assertEqual、assertTrue、assertIn、assertAlmostEqual……而pytest直接使用Python内置的assert关键字。我用一对例子来对比。unittest风格的写法import unittest class TestLogin(unittest.TestCase): def test_success(self): result login(admin, 123456) self.assertEqual(result[code], 200) self.assertIn(token, result)pytest风格的写法def test_success(): result login(admin, 123456) assert result[code] 200 assert token in result看起来只是语法上的简化但pytest对assert的失败信息做了大量增强。当断言失败时pytest会自动解析表达式告诉你具体哪个值等于哪个值而不是unittest那样只给一个AssertionError加一串模糊的消息。这一点在第4部分详细讲。写一个最小可运行的pytest用例def test_demo(): assert 1 1 2然后右键这个函数名点击Run pytest in test_demo.py。运行面板里应该看到绿色成功状态输出里包含1 passed。这就是pytest最小的闭环。3.3 三种运行方式对比跑测试有三种方式用途各不相同。我整理了一个对比表格运行方式操作路径适用场景优点缺点Pycharm右键运行右键测试文件/函数 - Run pytest in ...日常开发调试支持断点调试、失败时点击跳转源码无法灵活传命令行参数终端执行pytest -v -x验证完整测试套件、模拟CI行为参数灵活与CI环境行为一致费眼睛调试不方便Run Configuration配置固定参数后点击绿色按钮固定命令反复执行参数固化、结果输出可复用不够灵活改参数要进配置我的日常习惯是写用例、调代码时用Pycharm右键运行因为能打断点看变量定位问题特别快提交代码前或者要跑全量回归时切到终端跑pytest -v因为终端输出更接近CI里pipeline跑出来的效果能提前发现一些“在IDE里跑得好好的、到了CI上就挂”的隐性环境问题。3.4 结合断点调试用例Pycharm跑pytest一个巨大的优势就是在用例里打断点调试。点击代码行号左侧的空白处出现一个红色圆点即打断点。然后右键测试函数选择Debug pytest in test_demo.py程序会在断点处停住你能在Debug面板里查看每个变量的实时值单步执行查看调用栈。如果断言失败在测试输出面板里失败信息中的代码行号会变成超链接点击就能直接跳到对应的源码行。团队里排查别人写的用例失败原因时这个功能能省很多来回沟通的时间。用个小技巧如果你只在终端里跑想调试某个失败用例可以复制失败输出的用例ID然后在Pycharm的Run Configuration里用-k参数只跑那一个用例再打断点调试。组合起来用效率非常高。4. 核心进阶功能与实用插件4.1 断言失败信息阅读来做一个实验故意写一个会失败的用例def test_wrong(): assert 1 1 3跑起来失败输出长这样 assert 1 1 3 E assert (1 1) 3 E 1 E -3注意看1和-3这两行。pytest会把它推算出来的实际值和期望值明确标出来开头的实际值-是期望值。你不用去日志里翻数据一眼就知道哪个环节算错了。再举个例子字典和列表的断言失败信息更清晰def test_dict(): user {name: zhangsan, age: 18} assert user[age] 20输出会直接告诉你E age: 18 E age: 20这种差异高亮的效果比自己去打印对象再比对靠谱得多。这也解释了为什么pytest社区说“断言即报表”你不需要写额外的日志语句失败信息本身已经足够定位问题。4.2 fixture与conftest.py共享机制fixture是pytest的灵魂它是pytest区别于unittest最核心的特性。fixture解决的核心问题是测试用例执行前后的准备和清理工作把它抽离出来复用。我用一个实际例子来说明。假设我们的系统在测试时要先创建一个测试用户、结束后删除该用户多个测试文件都要用。在tests/conftest.py里定义import pytest pytest.fixture def test_user(): user {id: 1, name: test_user} # 这里是前置准备 create_user(user) # yield前面的代码是准备阶段yield返回数据给测试用例 yield user # yield后面的代码是清理阶段 delete_user(user[id])然后在任意测试文件里直接使用def test_get_user_info(test_user): assert test_user[id] 1 def test_update_user(test_user): update_user(test_user[id], {name: new_name}) assert get_user(test_user[id])[name] new_name注意test_get_user_info里并没有importtest_user因为conftest.py的作用域是自动传递给同一目录及其所有子目录下的测试文件的。这就是Pycharm里体现价值的地方右键点击测试函数里的fixture参数名可以跳转到conftest.py里的定义处方便阅读fixture做了哪些准备和清理工作。fixture的作用域有四个级别function默认每个用例执行一次、class每个类执行一次、module每个模块执行一次、session整个测试会话执行一次。比如初始化数据库连接这种重操作建议用session作用域避免每个用例都连接一次数据库拖慢整个测试套件。用法是在装饰器上指定pytest.fixture(scopesession) def db_connection(): conn create_connection() yield conn conn.close()4.3 参数化与标记参数化是什么场景用我想测试“登录接口”对不同的用户名密码组合的校验结果比如正确账号密码返回成功、错误密码返回失败、空用户名返回参数错误。如果一条条写用例维护成本高也没有意义。用parametrize一行装饰器搞定import pytest pytest.mark.parametrize(username,password,expected_code, [ (admin, 123456, 200), (admin, wrong, 401), (, 123456, 400), ]) def test_login(username, password, expected_code): resp login(username, password) assert resp[code] expected_code这组数据会生成3条测试用例每条用例名字里带上参数。跑pytest -v时你能看到test_login[admin-123456-200]这样的用例名称哪个组合失败一目了然。如果某组参数失败输出里会明确标注是哪一组参数引发的失败。标记mark是另一项实用功能。比如有某个用例是已知的、还没修的bug或者依赖外部环境、经常不稳定可以打标记跳过pytest.mark.skip(reason依赖的环境尚未就绪) def test_external_api(): ... pytest.mark.skipif(sys.version_info (3, 9), reason需要Python 3.9) def test_new_feature(): ...skip是无条件跳过skipif是条件满足才跳过xfail是预期失败跑挂了不算失败算作预期。用标记把不稳定的用例和确定性用例区分开执行时用-m参数控制跳过或单独执行。4.4 常用命令行参数pytest的命令行参数非常多先记住这几个高频的参数作用示例-v显示每个用例的详细执行结果pytest -v-s显示用例中的print输出pytest -s-k按名称关键字筛选用例pytest -k login or register-m按标记筛选用例pytest -m not slow--maxfail1遇到第一个失败就停止pytest --maxfail1--tbshort精简错误堆栈信息pytest --tbshort--collect-only只收集用例不执行pytest --collect-only--lf只重新运行上次失败的用例pytest --lf这几个参数组合起来能覆盖绝大多数日常场景。比如全量回归前只想跑和“order”相关的用例就用pytest -k order -v。改了某个模块的代码想只验证自己影响的用例用--lf重新跑上次失败的用例能省时间。5. 生成HTML报告与覆盖率统计5.1 安装并配置pytest-html测试跑完了结果不能只留在控制台里尤其是在团队协作或者需要留档的场景下。pytest-html插件可以生成一份独立的HTML报告直接发给同事或者挂到CI产物里都行。使用前先安装pip install pytest-html命令行执行pytest --htmlreport.html --self-contained-html这里有个关键参数--self-contained-html一定要加上。如果不加生成的HTML报告会依赖同目录下的assets样式文件夹你单独把report.html发给别人样式全丢页面看起来是纯文字的体验极差。加上这个参数后所有CSS和JS都内嵌在一个HTML文件里单文件可分享。在Pycharm里想把固定参数固化可以配置Run Configuration右键测试文件 -Edit pytest in test_xxx.py...在Additional arguments栏里填上--htmlreport.html --self-contained-html以后每次点绿色运行按钮就会自动生成报告。这个配置适合长期固定的参数组合不用每次手动敲。5.2 覆盖率统计pytest-cov覆盖率是衡量测试充分性的一个参考指标。pytest-cov插件能告诉你有多少代码行被测试执行到了。安装和基本用法pip install pytest-covpytest --covapp --cov-reporthtml--covapp指定要统计的源码目录是app--cov-reporthtml生成HTML格式的覆盖率报告。执行完后会在项目根目录生成htmlcov文件夹用浏览器打开htmlcov/index.html能看到每个文件的覆盖情况点进去还能看到每一行被标注为绿色已覆盖或红色未覆盖。覆盖率不是越高越好但它是发现“盲区”的好工具。比如你写了一个工具函数覆盖率显示这个函数有一半分支没被测试到那就说明测试用例不充分需要补用例。在实际项目中我一般把核心业务模块的覆盖率目标定在80%以上再高的边际收益会明显下降。5.3 多线程加速pytest-xdist当测试用例量很大比如上千条接口用例单线程跑可能要半小时。pytest-xdist用多进程并行执行能把这时间压缩到几分钟。安装后直接在命令行加-n参数指定并行数pytest -n autoauto表示自动使用CPU的核心数。也可以手动指定pytest -n 4注意并行不是银弹有两个前提要满足测试用例之间不能有顺序依赖且不能共享可变全局状态。比如说用例A修改了数据库里某条记录用例B的断言依赖A修改后的结果这就是顺序依赖并行跑必然挂。还有那些用同一个临时文件、同一个端口号的用例也不适合并行。另外fixture的作用域在并行下要特别小心。session作用域的fixture会在每个并行worker里各执行一次所以如果一个fixture做了数据库初始化工作它在并行模式下可能被重复初始化。我的处理方式是把只读的、不改变共享状态的fixture放session作用域所有写操作相关的fixture放到function作用域从根源上避免并行下的数据污染。6. 常见问题与排查技巧实录6.1 用例收集不到总是0 tests collected这是最常见的坑我在带新人时几乎每个人都踩过一遍。症状是Pycharm里点击运行结果显示collected 0 items或者干脆提示No tests were found。优先按下面的顺序排查检查项可能原因解决办法Default test runner还是unittestPycharm用unittest规则识别pytest用例Settings - Tools - Python Integrated Tools - 改为pytest文件名login.py这样不带test_前缀的文件不会被识别改成test_login.py或login_test.py函数名函数没有以test开头改为test_login_success()目录结构__init__.py缺失或者路径不在scan范围内在测试目录下加__init__.py在pytest.ini里配置testpaths解释器Pycharm选了解释器A终端用的是解释器BSettings里确认两者一致排查看不到问题时用终端执行pytest --collect-only -v它会打印出pytest实际看到了哪些目录和文件。如果这个输出也什么都没显示那问题的根源大概率在目录结构或pytest.ini配置上。6.2 终端能跑但Pycharm跑不了这个问题的典型报错是ModuleNotFoundError: No module named xxx但它有个迷惑性的前提在终端里同样的代码跑得好好的切到Pycharm就报模块找不到。出现这个情况基本是环境不一致。终端里用的Python和Pycharm里配置的Python不是同一个导致两边能看到的包不一样。解决方法是打开Settings - Project - Python Interpreter看当前选择的解释器路径和终端里which pythonWindows上是where python输出的路径是否一致。通常切换到虚拟环境解释器后问题就解决了。还有一个隐藏比较深的原因是工作目录不一致。Pycharm右键运行用例时Working directory取自Run Configuration里的配置。如果这个目录不是项目根目录Python的sys.path就不会包含项目根目录业务代码模块自然没法导入。修改Run Configuration里的Working directory或者在pytest.ini里显式配置pythonpath .需要pytest 7.0以上都能解决。6.3 Windows控制台编码问题在Windows环境下跑pytest如果用例输出中包含中文字符偶尔会报UnicodeEncodeError: gbk codec cant encode character。这是因为Windows控制台默认编码是GBK而输出里有GBK无法表示的字符。解决方案是在Pycharm的Run Configuration里设置环境变量右键测试 -Edit ...-Environment variables里添加PYTHONIOENCODINGutf-8这样pytest在输出日志时就会使用UTF-8编码问题解决。6.4 pytest.ini配置文件的推荐模板最后分享一个我常用的pytest.ini模板算是“部署”落地的最后一块拼图[pytest] testpaths tests python_files test_*.py *_test.py python_functions test_* norecursedirs venv .git build dist addopts -v --tbshort --maxfail5 markers smoke: 冒烟测试用例 regression: 回归测试用例 slow: 执行较慢的用例 filterwarnings ignore::DeprecationWarning逐条说明testpaths: 指定测试文件所在目录避免pytest扫描整个项目否则会去扫描venv里的文件。python_files/python_functions: 保持默认规则如果有特殊命名习惯可以在这里调整。norecursedirs: 告诉pytest不要递归扫描这些目录。addopts: 每次执行都自动加上的参数。我这里设定了-v显示详细结果、--tbshort精简堆栈、--maxfail5失败5个后停止。markers: 注册自定义标记避免控制台输出关于未知marker的warning。filterwarnings: 忽略指定的warning这里只写了忽略DeprecationWarning按需添加。配置文件写好后Pycharm会自动识别并使用它。你可以在Pycharm底部Terminal里输入pytest它就会按照配置文件里的规则执行不需要再敲一堆参数了。我个人在实际操作中最大的体会是pytest的部署和配置说难不难但坑全在细节里。命名规则、解释器路径、默认运行器、工作目录、配置文件任何一个环节没对上都会出莫名其妙的问题。按这篇文章的顺序走一遍把这些基础打好后面写用例、跑接口测试、看覆盖率报告整个流程会顺畅得多。下一步我在整理如何把pytest和requests结合做接口自动化测试以及如何优雅地管理登录态这类接口测试的长期token问题到时候再来更新测试笔记系列。
返回列表