惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

O
OpenAI News
I
InfoQ
云风的 BLOG
云风的 BLOG
博客园 - 【当耐特】
D
DataBreaches.Net
H
Help Net Security
爱范儿
爱范儿
F
Fortinet All Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
WordPress大学
WordPress大学
GbyAI
GbyAI
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园_首页
C
Check Point Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
G
Google Developers Blog
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
M
MIT News - Artificial intelligence
Recent Announcements
Recent Announcements
P
Proofpoint News Feed
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
MyScale Blog
MyScale Blog
Recorded Future
Recorded Future
B
Blog
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
Jina AI
Jina AI
博客园 - Franky
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
量子位
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
Cyberwarzone
Cyberwarzone
Google Online Security Blog
Google Online Security Blog
SecWiki News
SecWiki News
V
Vulnerabilities – Threatpost
AWS News Blog
AWS News Blog
Cisco Talos Blog
Cisco Talos Blog
G
GRAHAM CLULEY
T
Tor Project blog
腾讯CDC
美团技术团队
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News and Events Feed by Topic

BlogFinder

日常漫步 Vol.24 之漫步前山河 - 雅余 周报 #1-聊聊本周的收获 - Edwin's Blog 我的OpenCode必装插件与Skill Write Something 掌中之物未必在掌握之中 · CRIVU PiliNara,一个更顺手的 PiliPlus 分支 「NekoEcho」:做一个必有回响的猫娘主题博客 2026-05 书影音总结 简化博客主题 - 安迪 你要加油呐 我第一次发布 npm 包 拾花小记#45:中考前的二三事 – 小改学习志 黛西花园5月游 #18 枇杷又熟了的五月月报 一些奇奇怪怪的需求?word仿方正书版的几个小操作 - Xiobb's Blog 0419 御温泉之旅 修复了一些bug,网站基本上趋于稳定了 - 新锐博客 又回到四十年前 如何定义成功 迷鹿屋2026已重新上线 科技冰火两重天+一周回顾 ${title} 热度退了,我反而用得更深了-咕咚同学 我到底该不该换个域名? 随身WIFI折腾记 - 安迪 博客撰写体验提升——hexo pro插件 为什么不用相机把屏幕上的接关密码拍下来? 国清寺与天台山 – Ouroboros ★★★★☆《挽救计划》——久违的经济上行感 - Davidの3号基地 删除右键“打开方式”里多余选项 第三周刊_No.53|一切都会被支付两次 安卓APP通话记录与录音上传踩坑记录 - 子舒的博客 天量下跌 inBox 笔记 2.3.8,把工具栏交给了你-咕咚同学 我把小龙虾搬到了微信-咕咚同学 安好 - 响石潭 Compound Engineering Plugin:让每个工程单元都比上一个更容易 MOSS-TTS Family:开源高质量语音与声音生成模型家族深度解析 Crawl4AI:专为 LLM 设计的开源 Web 爬虫与数据抓取工具 Build Your Own X:从零实现你最喜欢的技术——程序员进阶的终极资源清单 Anthropic Skills:用文件夹教 Claude 专业技能的开源框架 1年的去月球(下) - 梅之夏 欢迎回来。 简单讲讲 ASN.1 与 OID DTV - 直播聚合客户端 5.22-5.27 – 不兴江 还没去过鸭川 – 不兴江 张晶晶同学三刷林志颖 关于我 – 不兴江 爱与嫉妒 – 不兴江 港股被持续做空 备案码花了四百块-咕咚同学 一句话生成封面:我给公众号做了4种风格的AI封面生成技能 「官」方認證 再谈费曼学习法 2026-05-28T00:34:11+08:00 2026-05-28T00:28:45+08:00 离谱的英语学习指南:基于AI的英语进阶系统方法论 iii:零集成架构的后端统一运行时 Claude Code Harness:让 Claude Code 工作有迹可循的工程化框架 Heretic:全自动移除大语言模型审查机制的开源工具 MarkItDown:微软开源的万能文档转 Markdown 利器 Harness:让 Claude Code 秒变多智能体协作工厂 这段时间尽折腾AI Agent了,确实极大地提高了效率 近期动态:两个新站点正式上线啦 误判解除!zhouayuan.com 腾讯安全申诉成功 - 周阿源|玩具设计・插画日常・生活随笔 Ralph:让 AI 编码工具自主循环跑完所有 PRD 任务的量产神器 全都违法 – 个人工作记录 关于zhouayuan.com被误判 “含违规信息” 的说明与申诉记录 - 周阿源|玩具设计・插画日常・生活随笔 小米 MiMo v2.5 Pro 白嫖 最大的人间清醒,兜里有钱,但是不花。 夜晚靓歌(12):于文文现场solo - 王志勇的Blog 今日插画:风扬起的倔强 - 周阿源|玩具设计・插画日常・生活随笔 回门习俗 独立网卡 - 忘记了回忆 500亿入股人工智能企业 从命令行到桌面智能体-咕咚同学 第一性原理读书笔记 行者微评论223-加班の守株待兔-博客|政治与时事-风雨行者 ZOZO开源物理接触求解器:GPU加速的可扩展仿真引擎 OpenStock:开源股票市场交易平台技术深度解析 MoneyPrinterTurbo:基于AI的全自动短视频生成工具深度解析 Claude-Mem:为 Claude Code 构建的持久化记忆压缩系统 Twenty:可代码化定制的企业级开源 CRM 平台技术深度解析 2026-05-26T22:59:17+08:00 企业级开源大模型部署平台 GPUStack 实战教程 1年的去月球(上) - 梅之夏 Sevalla - 静态网站托管服务 不用翻墙、不用注册、不用月费,普通人也能用上 Claude Code 装修灯具要注意⚠️ 黄梅天先锋 - 游子微博 公安备案顺利办结,站点备案全部完成 - 周阿源|玩具设计・插画日常・生活随笔 第三次兑换天猫超市卡了宗宗酱-三维狐少儿编程 Don't think, feel. - Rolen's Blog 人这一辈子,到底图个什么 博客迁移 - Edwin's Blog 情感赛道写作模板 再现本轮行情的典型特征 裁员与平常心-咕咚同学 别让“偷懒”,成为隐私泄露的破绽
pytest:Python 生态中最强大的测试框架,你真的用好了吗?
Cheman · 2026-06-14 · via BlogFinder

今天在 GitHub Trending 上看到一个经典项目再次上榜:pytest,Python 生态中拥有 58k+ Star 的测试框架,几乎每个 Python 开发者都会接触。但它远不止 assert 那么简单,今天就来深入看看它的设计精髓。

一、项目概述

pytest 是 Python 生态中最流行的测试框架,由 Holger Krekel 于 2004 年创建,采用 MIT 许可证开源。它让编写小测试变得极其简单,同时又能扩展支持复杂的应用和库级别的功能测试。

与 Python 标准库自带的 unittest 相比,pytest 的核心理念是:让测试代码像普通 Python 代码一样自然。你不需要继承 TestCase 类,不需要记住 self.assertEqualself.assertTrue 这些方法名,只需要用最朴素的 assert 语句即可。

核心特性包括:

  • 断言内省:失败的 assert 语句会自动显示变量值,无需手动编写错误信息
  • 自动发现:自动查找项目中所有符合命名规则的测试文件和测试函数
  • 模块化 Fixture:通过 @pytest.fixture 装饰器管理测试资源,支持作用域和参数化
  • 丰富的插件生态:1300+ 外部插件,覆盖覆盖率、并行执行、Mock、HTML 报告等场景
  • 兼容性:开箱即用支持运行 unittesttrial 测试套件
  • 最低 Python 3.10+,紧跟语言版本演进

二、技术原理

2.1 架构设计

pytest 的架构采用经典的 插件化管道 设计,整个测试执行过程分为多个阶段,每个阶段都可以通过 hook 函数进行扩展:

命令行解析 → 配置加载 → 测试收集 → 测试选择 → 测试执行 → 报告输出

核心模块位于 src/_pytest/ 目录,关键模块包括:

  • config.py:命令行参数解析与配置管理(_console_main 入口)
  • collection.py:测试发现与收集,递归遍历文件系统
  • runner.py:测试执行引擎,管理 fixture 生命周期
  • fixtures.py:Fixture 解析与依赖注入系统
  • assertion.py:断言重写(通过 AST 操作,在 assert 语句中插入诊断代码)

2.2 断言重写机制

pytest 最令人印象深刻的技术之一是 assertion rewriting。在 import 阶段,pytest 会通过 import hook 拦截测试模块,将其 AST(抽象语法树)中的 assert 语句改写为包含详细诊断信息的代码:

# 你写的代码
def test_answer():
    assert inc(3) == 5

# pytest 内部改写后大致等价于
def test_answer():
    if not (result := inc(3)) == 5:
        raise AssertionError(f"assert {result!r} == 5\n  where {result} = inc(3)")

这就是为什么 pytest 能在断言失败时显示清晰的变量值,而不需要你写任何额外的断言方法。这一机制在 src/_pytest/assertion/rewriting.py 中实现。

2.3 Fixture 依赖注入

pytest 的 Fixture 系统借鉴了依赖注入的思想。每个 fixture 是一个返回值的函数,通过函数参数声明依赖关系:

@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()

def test_query(db_connection):
    result = db_connection.execute("SELECT 1")
    assert result.fetchone()[0] == 1

Fixture 支持四种作用域:functionclassmodulepackagesession。通过 yield 语句实现资源清理,确保测试结束后正确释放资源。

pyproject.toml 中可以看到,pytest 自身的开发也大量使用了 fixture:

[tool.pytest]
minversion = "2.0"
addopts = ["-rfEX", "-p", "pytester"]

其中 -p pytester 加载了内置的 pytester 插件,专门用于测试 pytest 自身。

2.4 插件架构

pytest 的插件体系基于 Pluggy 库(pluggy>=1.5,<2),这是一个通用的 hook 管理库。插件通过实现特定的 hook 方法来介入测试生命周期的各个阶段:

# hookimpl 标记一个方法为 hook
@hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
    """在测试收集完成后、执行前修改测试顺序"""
    items.sort(key=lambda x: x.name)

从依赖来看,pytest 的核心依赖非常精简:

  • pluggy:Hook/插件系统
  • iniconfig:配置文件解析(pytest.ini)
  • packaging:版本号比较
  • pygments:终端彩色输出

2.5 工程质量

pytest 自身的工程质量堪称教科书级别。从 pyproject.toml 可以看到:

  • 类型检查:同时使用 mypy(strict 模式)和 pyrightcheck_untyped_defs = truedisallow_untyped_defs = true
  • 代码格式:Ruff + Black 双重保障
  • 代码覆盖率:使用 coverage.py,启用 branch = true,覆盖率可通过 CI badge 查看
  • 预提交检查:集成了 pre-commit.ci
  • CI:通过 GitHub Actions 持续运行测试

三、安装与快速开始

环境要求

  • Python 3.10 或更高版本(支持到 Python 3.15)
  • pip 包管理器

安装

# 基础安装
pip install pytest

# 可选:安装开发依赖
pip install pytest[dev]

最简示例

创建一个 test_sample.py 文件:

def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 4

运行测试:

$ pytest
============================= test session starts =============================
collected 1 items

test_sample.py .                                                          [100%]

============================== 1 passed in 0.04s ==============================

只需 assert 语句,无需继承任何类,无需调用任何 runner——这就是 pytest 的优雅之处。

四、使用方法与实战

4.1 参数化测试

pytest 的 @pytest.mark.parametrize 装饰器让你用一组数据驱动同一个测试:

@pytest.mark.parametrize("input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
])
def test_eval(input, expected):
    assert eval(input) == expected

4.2 Fixture 高级用法

@pytest.fixture(scope="session")
def redis_client():
    client = redis.Redis()
    client.flushall()  # 测试前清空
    yield client
    client.close()

@pytest.fixture
def cached_data(redis_client):
    # 依赖另一个 fixture
    return redis_client.get("test_key")

4.3 标记与筛选

@pytest.mark.slow
def test_large_dataset():
    ...

@pytest.mark.skipif(sys.platform != "linux", reason="Linux only")
def test_linux_feature():
    ...

运行时按标记筛选:

pytest -m "not slow"          # 跳过慢测试
pytest -k "test_login"        # 按名称匹配

4.4 conftest.py 共享 Fixture

在项目目录下创建 conftest.py,其中的 fixture 会被该目录及子目录的所有测试自动发现和使用,无需 import。这是 pytest 实现测试资源共享的核心机制。

五、常见问题与解决方案

Q1:安装后 pytest 命令找不到?

确保安装时 Python 环境正确激活。在虚拟环境中安装后,需要激活该虚拟环境:

source venv/bin/activate
which pytest  # 应显示虚拟环境路径

Q2:fixture 未找到?

检查 conftest.py 是否放在正确的目录层级中。fixture 的可见范围取决于 conftest.py 的位置——放在项目根目录的 conftest.py 对所有测试可见。

Q3:测试收集不到我的测试函数?

pytest 默认收集以 test_ 开头或 _test 结尾的文件,以及以 Test 开头的类中以 test_ 开头的方法。如果你的文件不符合命名规则,可以通过 pyproject.toml 自定义:

[tool.pytest]
python_files = ["test_*.py", "check_*.py"]

Q4:如何处理异步测试?

安装 pytest-asyncio 插件:

pip install pytest-asyncio
@pytest.mark.asyncio
async def test_async_operation():
    result = await async_func()
    assert result is not None

Q5:与 unittest 如何共存?

pytest 可以直接运行 unittest 测试用例,无需任何修改。但如果你想在 unittest 风格的测试中使用 pytest 的 fixture,可以通过 pytestmark 或混合继承的方式实现。

六、总结

pytest 之所以能成为 Python 测试的事实标准,不仅因为它让写测试变简单了,更在于它深厚的工程设计:

  1. AST 级别的断言重写让最朴素的 assert 拥有媲美 self.assert* 的诊断能力
  2. 依赖注入式的 Fixture 系统优雅地解决了测试资源管理问题
  3. 基于 Pluggy 的插件架构让扩展变得自然而不侵入
  4. 自身代码的超高质量(strict mypy、branch coverage、CI/CD 全覆盖)证明了它践行自己倡导的理念

如果你还在用 unittest 写测试,不妨给 pytest 一个机会——从把 self.assertEqual 改成 assert 开始,你会发现测试也可以是一件优雅的事。

📦 项目地址:github.com/pytest-dev/pytest 📖 官方文档:docs.pytest.org