news 2026/9/12 9:59:43

10分钟跑通你的第一个pytest测试:Python测试框架手把手教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
10分钟跑通你的第一个pytest测试:Python测试框架手把手教程

10分钟跑通你的第一个pytest测试:Python测试框架手把手教程

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

改完一行折扣逻辑提交后,用户反馈账单金额不对。你手动在控制台里重跑几个输入找原因,却容易漏掉别的分支。pytest测试框架解决的就是这个问题:把你关心的"输入 → 期望输出"写成一个个函数,改完代码后一条命令全部重跑,并精确告诉你哪一行错了。本教程会帮你用10分钟跑通第一个测试,再学会断言、夹具、参数化三招,最后为项目配上pytest.ini,让它长期稳定运行。

🏁 跑通第一个测试:装好pytest并读懂运行输出

确认 Python 3.10 以上后,一条命令装好 pytest 测试框架:

pip install pytest

写一个贴近真实业务的最小用例。这里用一个折扣函数代替玩具例子,测试文件和被测函数可以放在同一个文件里快速验证:

# test_pricing.py def discount(price: float, coupon: float) -> float: return max(price - coupon, 0.0) # 优惠券不能把金额扣成负数 def test_coupon_deducts_from_price(): assert discount(100.0, 20.0) == 80.0 # 正常抵扣 def test_coupon_cannot_go_negative(): assert discount(15.0, 20.0) == 0.0 # 金额下限为 0

在项目根目录敲pytest -v-v表示逐条列出每个用例的结果),你会看到:

=========================== test session starts ============================ platform linux -- Python 3.12, pytest-9.0.x, pluggy-1.6 -- /home/you/.venv/bin/python cachedir: .pytest_cache rootdir: /home/you/checkout collecting ... collected 2 items tests/test_pricing.py::test_coupon_deducts_from_price PASSED [ 50%] tests/test_pricing.py::test_coupon_cannot_go_negative PASSED [100%] ============================== 2 passed in 0.03s ===============================

前三行是环境信息:platform显示 Python 和 pytest 版本,rootdir是 pytest 定位到的项目根目录(配置文件所在处)。collected 2 items表示它按文件名和函数名收集到了 2 个用例——文件要叫test_*.py,函数要叫test_*,这是收集规则。最后一行的passed in 0.03s是总结论:全部通过,耗时 0.03 秒。

🧩 看懂pytest内部结构:一张表讲清6个核心模块

pytest 的所有核心代码都在src/_pytest/下。一次运行大致按这个顺序流转:

pytest(命令行入口) └─ config 解析参数、加载 pytest.ini、定位 rootdir └─ collect 扫描 test_*.py,收集出一个个用例 └─ runner 逐个执行 ├─ fixtures 用例运行前准备、运行后清理 ├─ assertion 改写 assert,失败时给出详细对比 └─ terminal 把结果渲染成上面的终端报告

对应到真实目录,主要模块如下(路径均相对仓库根目录):

模块职责真实路径
config解析命令行参数、加载配置、定位项目根src/_pytest/config/
fixtures夹具系统:依赖注入与生命周期管理src/_pytest/fixtures.py
assertion断言重写,失败时生成可读的对比信息src/_pytest/assertion/
mark@pytest.mark标记与按标记筛选src/_pytest/mark/
runner执行每个测试项并收集结果src/_pytest/runner.py
terminal渲染.PASSED等终端报告src/_pytest/terminal.py

你平时只和命令行交互,但这张表解释了"为什么-m筛选生效""为什么失败信息这么详细"——分别对应 mark 和 assertion 两个模块。

✍️ 写好用例的三个进阶招式:断言、夹具、参数化

招式一:靠断言重写读懂失败现场

一句话价值:pytest 会在收集阶段改写你的assert,失败时自动打印两边实际值,不用你手写打印。

把折扣函数的max删掉模拟一个 bug,重新运行:

tests/test_pricing.py:9: in test_coupon_cannot_go_negative assert discount(15.0, 20.0) == 0.0 E assert -5.0 == 0.0

E行直接告诉你:函数返回了-5.0而不是期望的0.0,定位不到问题就不用猜。涉及浮点数时改用近似比较:

from pytest import approx assert 0.1 + 0.2 == approx(0.3) # 浮点误差范围内视为相等

什么时候该用:所有断言场景。普通==适合整数、字符串、结构体;浮点运算、金额计算一律approx。改写机制的实现在 src/_pytest/assertion/。

招式二:用夹具替代手写setup/teardown

一句话价值:夹具(fixture)让你把"准备测试数据"的代码从每个用例里抽出来,按函数名注入。

import pytest @pytest.fixture def invoice(): # 夹具名就是测试函数的参数名 return {"items": ["keyboard", "mouse"], "amount": 499.0} def test_invoice_amount_positive(invoice): # 参数名对上即自动注入 assert invoice["amount"] > 0

夹具还支持作用域:@pytest.fixture(scope="session")表示整个会话只初始化一次,适合数据库连接这类昂贵的资源。

什么时候该用:同一个用例要准备超过 3 行数据,或者多个用例共享同一份数据时。实现入口在 src/_pytest/fixtures.py。

招式三:用参数化消灭复制粘贴

一句话价值:同一个断言逻辑要验证多组输入时,用@pytest.mark.parametrize写一次、跑 N 遍。

import pytest @pytest.mark.parametrize( ("price", "coupon"), [(100.0, 20.0), (15.0, 20.0), (9.9, 5.0)], # 每组数据独立跑一遍下面的函数 ) def test_discount_never_negative(price, coupon): assert discount(price, coupon) >= 0.0

运行后报告会列出 3 条用例,哪组数据挂了一目了然。

什么时候该用:边界值测试(0、负数、上限)、多版本兼容、不同配置的对比。只要发现自己在复制测试函数只改数字,就该参数化。

🔧 让测试跑稳:pytest.ini配置与报告命令

用pytest.ini固定项目默认行为

在项目根目录建pytest.ini,团队所有人跑pytest时自动带上这些设定:

[pytest] addopts = -v --tb=short # 默认逐条显示,失败信息用短格式 testpaths = tests # 不传路径时默认扫 tests 目录 pythonpath = src # 把 src 加入导入路径 markers = slow: 慢速用例,用 -m "not slow" 跳过

配合目录组织,项目保持这种结构即可:

myproject/ ├── src/myproject/ # 源码包 ├── tests/ # 测试,文件名 test_*.py │ └── test_pricing.py └── pytest.ini

用命令行控制每次怎么跑

pytest -m "not slow" # 只跳过标记为 slow 的用例,其余照跑 pytest -x # 第一个失败就停下,适合逐条修复 pytest --lf # 只重跑上次失败的那些(依赖 .pytest_cache) pytest --junitxml=report.xml # 输出 XML 报告给 CI 平台消费

--lf依赖缓存模块 src/_pytest/cacheprovider.py,第一次跑完会在.pytest_cache/里记录失败清单,日常迭代用它最省时间。

⚠️ 新手最容易踩的几个坑:5个高频问题

Q:为什么报collected 0 items,一个测试都没跑?收集规则是死约定:文件必须以test_开头、函数必须以test_开头,且不能带多余参数(test_foo(self)这种 unittest 写法在这里无效)。改对命名后先跑pytest --collect-only确认收集到了再执行。

Q:运行报ModuleNotFoundError,但代码里 import 明明没问题?pytest 的 rootdir 和导入路径和你在 IDE 里点运行不是一回事。把源码放在src/下时,在pytest.ini里加一行pythonpath = src即可;或者用pip install -e .把项目装进当前环境。

Q:两个目录里各有一个同名test_helper.py,运行直接报错?这是导入冲突:非包目录里的同名文件会撞车。给目录加__init__.py组成包,或者把测试文件重命名成全局唯一。详细的导入机制见 doc/en/explanation/pythonpath.rst。

Q:夹具明明写了,用例却提示找不到对应参数?注入靠参数名精确匹配:@pytest.fixture的函数名必须和用例参数名完全一致。另外夹具定义在别的测试文件里时不共享,公共夹具要放到tests/conftest.py中。

Q:改了代码重跑太慢,怎么只跑刚才失败的那批?pytest --lf(last-failed)。注意它只认缓存里的记录:一次全部通过后缓存清空,--lf会退化成全量运行,这属于正常行为。

pytest 用"文件即用例、断言即检查、夹具即环境"三件事,覆盖了大部分日常测试需求。下一步可以:看 doc/en/how-to/writing_plugins.rst 写自己的插件、研究testing/plugins_integration/里与 Django 等框架的集成方式、或用pytest --benchmark类插件给热点函数做性能基准。

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 9:58:51

结构化提示技术在代码语义分析与系统重构中的应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:58:29

CMSIS-FreeRTOS源码静态审计:嵌入式RTOS可信度深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:57:54

C++内存管理核心机制与智能指针实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华