1. 从“写测试”到“写好测试”为什么我们需要pytest如果你写过Python代码尤其是写过一些稍微有点规模的脚本或者应用那么“写测试”这个概念对你来说应该不陌生。可能你用过Python自带的unittest框架或者干脆就是写几个if语句手动调用函数看看输出对不对。刚开始这么做没问题但随着代码量增长、功能模块增多这种“手动测试”的方式很快就会让你头疼每次改点代码都得把之前的功能全部手动跑一遍费时费力还容易遗漏。这时候一个自动化测试框架的价值就体现出来了。它帮你把测试用例组织起来一键运行自动告诉你哪些通过了哪些失败了。而pytest就是目前Python社区里最流行、也最受开发者喜爱的测试框架没有之一。它不像unittest那样需要你继承特定的类、写一堆self.assertXXX它的哲学是“约定优于配置”和“极简主义”。简单来说就是用更少的代码做更多的事并且让测试写起来像写普通Python函数一样自然流畅。我刚开始接触自动化测试时也用过unittest但自从用了pytest就再也回不去了。它那种“发现即运行”的智能、丰富的断言内省、强大的夹具Fixture系统以及海量的第三方插件生态能让你把精力真正集中在测试逻辑本身而不是框架的条条框框上。无论你是测试一个简单的工具函数还是一个复杂的Web应用或数据处理管道pytest都能提供得心应手的工具。接下来我们就从零开始彻底搞懂它。2. pytest核心优势与设计哲学拆解在动手安装和写代码之前我们先花点时间理解一下pytest到底“好”在哪里。这能帮助你在后续使用中更好地利用它的特性而不是仅仅把它当作一个unittest的替代品。2.1 极简的语法与强大的断言这是pytest最吸引人的一点。在unittest里你需要写self.assertEqual(a, b)、self.assertTrue(x)。而在pytest里你只需要使用Python原生的assert语句。# unittest 风格 def test_old_way(self): self.assertEqual(add(1, 2), 3) self.assertTrue(is_even(4)) # pytest 风格 def test_new_way(): assert add(1, 2) 3 assert is_even(4) is True看起来只是少写了几个字母远不止如此。当断言失败时pytest会给出极其详细的、人类可读的错误信息。例如如果assert user.name “Alice”失败了pytest不仅会告诉你两边不相等还会把user.name的实际值比如”Bob”清晰地打印出来。而unittest的assertEqual通常只给你一个简单的AssertionError。这个“断言重写”机制是pytest的魔法之一它让你调试测试失败时省去了大量打印中间值的麻烦。2.2 智能的测试发现pytest是如何找到你的测试的它遵循一套简单而有效的约定在当前目录及其子目录中寻找文件名以test_开头或者以_test.py结尾的文件。在这些文件中寻找以test_开头的函数。也会识别Test开头的类并且该类不能有__init__方法中的以test_开头的方法。这意味着你几乎不需要任何配置。只要按这个规则命名pytest就能自动发现并运行它们。你可以把测试文件放在任何合理的目录结构里框架都能理解。2.3 革命性的夹具Fixture系统夹具是pytest的灵魂功能用于测试的“准备”和“清理”工作。你可以把它理解为一种更强大、更灵活的setUp和tearDown。import pytest pytest.fixture def database_connection(): # 准备工作建立数据库连接 conn create_connection(‘test_db‘) yield conn # 这是提供给测试用例使用的值 # 清理工作测试结束后关闭连接 conn.close() def test_query_user(database_connection): # 夹具通过函数参数自动注入 result database_connection.execute(“SELECT * FROM users”) assert len(result) 0它的强大之处在于依赖注入测试函数通过声明参数来“请求”需要的夹具框架自动完成注入代码非常清晰。作用域控制一个夹具可以只执行一次然后被多个测试用例共享scope”module”或”session”极大提升了测试速度尤其是在需要连接外部服务数据库、API时。可组合性夹具本身可以依赖其他夹具构建出复杂的测试环境。2.4 丰富的插件生态pytest本身是一个核心小巧但扩展性极强的框架。社区有超过1000个插件可以满足各种特殊需求pytest-cov: 生成测试覆盖率报告。pytest-xdist: 并行运行测试加速大型测试套件。pytest-mock: 集成unittest.mock方便打桩和模拟。pytest-asyncio: 测试异步代码。pytest-django,pytest-flask: 专门用于Django或Flask应用的测试。这种“核心插件”的模式让pytest既能保持简单又能应对各种复杂场景。2.5 与unittest的良好兼容你现有的unittest测试用例不需要重写pytest可以直接运行它们并且通常能提供比原生unittest运行器更好的错误输出。这为项目迁移提供了平滑的过渡路径。3. 环境准备与pytest安装全指南好了理论部分先到这里我们开始动手。安装pytest非常简单但围绕它构建一个高效、可复现的测试环境有一些最佳实践值得遵循。3.1 创建独立的虚拟环境这是Python项目开发的第一条军规对于测试来说尤其重要。你绝不希望因为系统Python环境里某个库的版本冲突导致测试结果莫名其妙地失败。使用venvPython 3.3 内置# 在你的项目根目录下 python -m venv .venv # 激活虚拟环境 # 在 Windows 上 .venv\Scripts\activate # 在 macOS/Linux 上 source .venv/bin/activate激活后你的命令行提示符前通常会显示(.venv)表示你已进入该虚拟环境。注意务必把虚拟环境目录如.venv添加到你的.gitignore文件中不要将其提交到版本控制系统。3.2 使用pip安装pytest在激活的虚拟环境中执行安装命令pip install pytest这行命令会安装pytest核心框架及其基础依赖。安装完成后可以通过以下命令验证pytest --version正常情况下它会输出pytest的版本号例如pytest 8.0.0。3.3 使用requirements.txt管理依赖对于正式项目建议将依赖固定下来。创建一个requirements.txt文件或更现代的pyproject.toml并记录pytest。# requirements.txt pytest7.4.0然后使用pip install -r requirements.txt来安装。你可以使用pip freeze requirements.txt来生成当前环境的所有包但更推荐手动维护一个精简的、只包含项目直接依赖的列表并使用来指定最低版本保持一定的灵活性。3.4 可选但推荐的插件安装根据你的项目类型可以考虑一并安装一些常用插件# 测试覆盖率 pip install pytest-cov # 并行测试 pip install pytest-xdist # 用于模拟对象 pip install pytest-mock你可以将它们一并加入requirements.txt的[dev]或[test]额外依赖部分。4. 第一个测试从“Hello, Test!”到实际项目结构现在让我们告别空洞的例子写一个贴近真实场景的测试。假设我们有一个简单的项目用于处理用户数据。4.1 项目结构规划一个清晰的项目结构有助于长期维护。推荐如下结构my_project/ ├── .venv/ # 虚拟环境.gitignore忽略 ├── src/ # 项目源代码 │ └── my_project/ │ ├── __init__.py │ └── user_manager.py # 我们的业务逻辑模块 ├── tests/ # 测试目录 │ ├── __init__.py # 让pytest将tests识别为包可选但常见 │ ├── conftest.py # 放置夹具的专用文件 │ ├── test_user_manager.py # 针对user_manager的测试 │ └── integration/ # 集成测试目录 └── pyproject.toml # 项目配置和依赖声明4.2 编写被测试代码先看看我们的“业务逻辑”是什么。在src/my_project/user_manager.py中# src/my_project/user_manager.py class User: def __init__(self, username, email, is_activeTrue): self.username username self.email email self.is_active is_active class UserManager: def __init__(self): self.users [] def add_user(self, username, email): 添加用户用户名不能重复 if any(u.username username for u in self.users): raise ValueError(f“Username ‘{username}‘ already exists.”) new_user User(username, email) self.users.append(new_user) return new_user def get_user_by_username(self, username): 根据用户名查找用户 for user in self.users: if user.username username: return user return None def deactivate_user(self, username): 停用用户 user self.get_user_by_username(username) if user: user.is_active False return True return False这是一个非常简单的用户管理类有添加、查找和停用用户的功能。4.3 编写第一个测试文件现在在tests/test_user_manager.py中编写我们的测试# tests/test_user_manager.py import sys import os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ‘../src‘))) from my_project.user_manager import UserManager def test_add_user_success(): 测试成功添加用户 manager UserManager() user manager.add_user(“alice”, “aliceexample.com”) assert user.username “alice” assert user.email “aliceexample.com” assert user.is_active is True assert len(manager.users) 1 assert manager.users[0] is user # 确认返回的对象就是存储的对象 def test_add_user_duplicate_username(): 测试添加重复用户名应抛出异常 manager UserManager() manager.add_user(“bob”, “bobexample.com”) # 使用 pytest.raises 来断言会抛出特定异常 import pytest with pytest.raises(ValueError, match“Username ‘bob‘ already exists.”): manager.add_user(“bob”, “another_bobexample.com”) # 确认用户数量没有增加 assert len(manager.users) 1 def test_get_user_exists(): 测试查找存在的用户 manager UserManager() added_user manager.add_user(“charlie”, “charlieexample.com”) found_user manager.get_user_by_username(“charlie”) assert found_user is added_user # 应该是同一个对象 def test_get_user_not_exists(): 测试查找不存在的用户应返回None manager UserManager() found_user manager.get_user_by_username(“david”) assert found_user is None def test_deactivate_user(): 测试停用用户 manager UserManager() manager.add_user(“eve”, “eveexample.com”) result manager.deactivate_user(“eve”) assert result is True user manager.get_user_by_username(“eve”) assert user.is_active is False def test_deactivate_nonexistent_user(): 测试停用不存在的用户应返回False manager UserManager() result manager.deactivate_user(“nobody”) assert result is False代码解析与技巧开头的sys.path修改因为我们的项目结构是src布局测试文件需要将src目录添加到Python路径才能正确导入my_project模块。这是一种常见做法。更现代的方式是使用pip install -e .以可编辑模式安装项目本身。测试函数命名都以test_开头这是pytest发现的规则。清晰的断言直接使用assert条件非常直观。测试异常使用pytest.raises作为上下文管理器来断言代码块会抛出指定的异常并且可以用match参数来匹配异常信息中的部分文本。测试独立性每个测试函数都创建自己全新的UserManager实例。这是一个好习惯确保测试之间不会相互干扰。后面我们会用夹具来优化这个模式。4.4 运行测试并解读输出在项目根目录my_project/下打开终端并确保虚拟环境已激活然后运行pytestpytest会自动发现tests/目录下的所有测试并运行。你会看到类似下面的输出 test session starts platform darwin -- Python 3.9.0, pytest-8.0.0, pluggy-1.0.0 rootdir: /path/to/my_project collected 6 items tests/test_user_manager.py ...... [100%] 6 passed in 0.02s 这太棒了6个测试全部通过用点.表示。每个点代表一个通过的测试。让我们故意制造一个失败。修改test_add_user_success中的一行assert user.email “wrong_emailexample.com” # 错误的断言再次运行pytest你会看到丰富的失败信息... (前面省略) ... FAILED tests/test_user_manager.py::test_add_user_success - AssertionError: assert ‘aliceexample.com‘ ‘wrong_emailexample.com‘ - wrong_emailexample.com aliceexample.compytest清晰地展示了断言的两边值并用-和标出了差异。这就是断言重写带来的调试便利性。你可以使用一些有用的命令行选项pytest -v: 以详细模式运行会显示每个测试用例的名字而不是点。pytest tests/test_user_manager.py::test_add_user_duplicate_username: 运行指定文件的指定测试函数。pytest -k “add_user”: 运行名字中包含“add_user”的所有测试关键字过滤。pytest --tbshort: 当测试失败时显示更简短的追溯信息。5. 深入核心夹具Fixture的实战应用与优化前面每个测试都自己创建UserManager实例如果创建过程很复杂比如需要读取配置、连接网络就会产生大量重复代码。这时就该夹具登场了。5.1 创建基础夹具我们在tests/conftest.py文件中定义夹具。这个文件的名字是固定的pytest会自动发现其中的夹具供所有测试文件使用。# tests/conftest.py import pytest from my_project.user_manager import UserManager pytest.fixture def user_manager(): 提供一个干净的UserManager实例 return UserManager() pytest.fixture def sample_user(user_manager): # 夹具可以依赖其他夹具 提供一个已添加的示例用户 return user_manager.add_user(“fixture_user”, “fixtureexample.com”)5.2 在测试中使用夹具修改tests/test_user_manager.py去掉手动创建实例的代码改为使用夹具# tests/test_user_manager.py import sys import os sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ‘../src‘))) import pytest from my_project.user_manager import UserManager # 不再需要 import pytest for pytest.raises因为文件顶部已经import了 def test_add_user_success(user_manager): # 通过参数请求夹具 user user_manager.add_user(“alice”, “aliceexample.com”) assert user.username “alice” assert user.email “aliceexample.com” assert len(user_manager.users) 1 def test_add_user_duplicate_username(user_manager): user_manager.add_user(“bob”, “bobexample.com”) with pytest.raises(ValueError, match“Username ‘bob‘ already exists.”): user_manager.add_user(“bob”, “another_bobexample.com”) assert len(user_manager.users) 1 def test_get_user_exists(user_manager, sample_user): # 可以使用多个夹具 # sample_user 夹具已经通过user_manager添加了一个用户 found_user user_manager.get_user_by_username(“fixture_user”) assert found_user is sample_user assert len(user_manager.users) 1 # 注意user_manager是独立的sample_user的添加操作已经影响了它 def test_get_user_not_exists(user_manager): found_user user_manager.get_user_by_username(“david”) assert found_user is None def test_deactivate_user(user_manager, sample_user): result user_manager.deactivate_user(“fixture_user”) assert result is True user user_manager.get_user_by_username(“fixture_user”) assert user.is_active is False def test_deactivate_nonexistent_user(user_manager): result user_manager.deactivate_user(“nobody”) assert result is False关键变化测试函数签名中增加了user_manager和/或sample_user参数。pytest在运行测试时会识别这些参数名并自动从conftest.py或当前文件中找到同名的夹具函数执行并将其返回值注入到参数中。test_get_user_exists和test_deactivate_user现在依赖于sample_user夹具这个夹具又依赖于user_manager夹具。pytest会按正确的依赖顺序执行它们。测试函数内部不再有manager UserManager()这样的代码逻辑更清晰专注于测试行为本身。5.3 夹具的作用域Scope与生命周期管理默认情况下夹具在每个测试函数级别执行一次。这意味着对于user_manager夹具每个测试函数都会得到一个新的、独立的UserManager实例。这保证了测试的隔离性但有时效率低下。例如如果初始化一个数据库连接非常耗时我们希望所有测试共享同一个连接。这时可以使用scope参数scope”function”: (默认) 每个测试函数运行一次。scope”class”: 每个测试类运行一次。scope”module”: 每个测试模块文件运行一次。scope”session”: 整个测试会话一次pytest命令运行一次。# tests/conftest.py import pytest import sqlite3 from my_project.user_manager import UserManager # 假设现在UserManager需要数据库连接 pytest.fixture(scope”session”) # 整个测试过程只创建一次 def database_connection(): 创建一个到临时测试数据库的连接 conn sqlite3.connect(“:memory:”) # 内存数据库速度快且隔离 # 可能在这里初始化表结构 yield conn conn.close() # 所有测试结束后关闭连接 pytest.fixture def user_manager(database_connection): # 依赖session级别的夹具 提供一个与数据库关联的UserManager实例 # 假设UserManager现在接收一个连接参数 manager UserManager(db_conndatabase_connection) # 可能在这里为这个manager做一些初始化 yield manager # 可以在这里为这个manager做清理比如清空其管理的临时数据 manager.clear_all_users() # 假设有这个方法重要提示扩大夹具作用域可以提升性能但必须非常小心。你必须确保测试不会修改共享夹具的状态或者如果修改了要在测试完成后能正确地清理或重置状态否则会导致测试间相互污染产生难以调试的、依赖于执行顺序的失败“flaky tests”。对于像UserManager这样内部有状态self.users列表的对象通常更适合使用默认的function作用域。5.4 夹具的自动使用autouse有些夹具你希望在某些测试中自动生效而不需要显式声明为参数。比如为所有测试设置一个临时工作目录或者打一个全局的补丁monkeypatch。pytest.fixture(autouseTrue, scope”function”) def setup_and_teardown_temp_dir(tmp_path): # tmp_path是pytest内置夹具提供一个临时路径 每个测试自动执行进入临时目录测试后返回原目录 import os original_cwd os.getcwd() temp_dir tmp_path / “test_run” temp_dir.mkdir() os.chdir(temp_dir) yield os.chdir(original_cwd) # 测试结束后切回原目录设置了autouseTrue的夹具其yield之前的代码会在每个匹配作用域的测试之前运行yield之后的代码会在测试之后运行。tmp_path是pytest提供的另一个非常有用的内置夹具。6. 高级断言、参数化测试与标记掌握了基础测试和夹具后我们来看看pytest如何让测试变得更强大、更简洁。6.1 更丰富的断言方式除了简单的assert a bpytest支持所有Python表达式并且对复杂数据结构有很好的支持。def test_complex_assertions(): # 断言容器内容 result_list [1, 2, 3] assert 2 in result_list assert 4 not in result_list assert result_list [1, 2, 3] # 列表顺序敏感 assert len(result_list) 3 # 断言字典 result_dict {“a”: 1, “b”: 2} assert result_dict[“a”] 1 assert “b” in result_dict assert result_dict.get(“c”, “default”) “default” # 断言浮点数近似相等非常重要 import math assert math.pi 3.141592653589793 # 严格相等可能失败 # 使用 pytest.approx from pytest import approx assert math.pi approx(3.14159, rel1e-5) # 相对误差 assert 0.1 0.2 approx(0.3) # 解决浮点数精度问题 # 断言异常信息包含特定文本 with pytest.raises(ValueError) as exc_info: int(“not_a_number”) assert “invalid literal” in str(exc_info.value) # 断言警告Python 3 import warnings with warnings.catch_warnings(recordTrue) as w: warnings.warn(“This is a deprecation warning”, DeprecationWarning) assert len(w) 1 assert issubclass(w[0].category, DeprecationWarning) assert “deprecation” in str(w[0].message)6.2 参数化测试用一组数据测试多种情况当你需要用多组不同的输入和期望输出来测试同一个功能时pytest.mark.parametrize装饰器是终极利器。它能避免你写一堆看起来重复的测试函数。假设我们有一个函数parse_number用来解析字符串并返回数字。# src/my_project/calculator.py (假设) def parse_number(s): try: return int(s) except ValueError: try: return float(s) except ValueError: raise ValueError(f“Cannot parse ‘{s}‘ as a number”) # tests/test_calculator.py import pytest from my_project.calculator import parse_number pytest.mark.parametrize(“input_str, expected_output”, [ (“42”, 42), # 整数 (“-7”, -7), # 负整数 (“3.14”, 3.14), # 浮点数 (“0”, 0), # 零 (“ 123 “, 123), # 带空格 ]) def test_parse_number_valid(input_str, expected_output): 测试有效的数字字符串 assert parse_number(input_str) expected_output pytest.mark.parametrize(“invalid_input”, [ “hello”, “12a34”, “”, “3.14.15”, ]) def test_parse_number_invalid(invalid_input): 测试无效的输入应抛出ValueError with pytest.raises(ValueError): parse_number(invalid_input)运行测试时pytest会为参数化装饰器中的每一组数据单独生成并运行一个测试用例。在输出中你会看到类似test_parse_number_valid[42]、test_parse_number_valid[-7]这样的测试项非常清晰。这极大地提高了测试的覆盖率和代码的简洁性。6.3 使用标记Mark分类和筛选测试你可以给测试函数打上“标签”然后选择性地运行它们。# tests/test_user_manager.py import pytest pytest.mark.slow # 自定义标记慢速测试 def test_complex_user_import(user_manager): 模拟一个非常耗时的用户导入操作 import time time.sleep(2) # 模拟耗时操作 # ... 测试逻辑 ... assert True pytest.mark.integration # 自定义标记集成测试 def test_user_manager_with_real_db(database_connection): 使用真实数据库非内存数据库的集成测试 # ... 测试逻辑 ... assert True pytest.mark.parametrize(“username”, [“alice”, “bob”, “charlie”]) pytest.mark.quick # 可以和参数化一起使用 def test_add_various_users(user_manager, username): user user_manager.add_user(username, f“{username}test.com”) assert user.username username如何使用标记运行所有测试pytest只运行标记为quick的测试pytest -m quick运行除了标记为slow以外的测试pytest -m “not slow”运行integration或slow的测试pytest -m “integration or slow”注册自定义标记为了避免拼写错误最好在项目根目录的pytest.ini或pyproject.toml文件中注册你的标记。# pytest.ini [pytest] markers slow: marks tests as slow (deselect with ‘-m “not slow”‘) integration: marks tests as integration tests with external dependencies quick: quick-running unit tests7. 常见问题、调试技巧与最佳实践即使有了强大的工具写测试过程中还是会遇到各种问题。这里记录了一些常见坑点和实用技巧。7.1 测试导入失败ModuleNotFoundError这是新手最常见的问题。根本原因是Python解释器找不到你的模块。解决方案推荐方案可编辑模式安装在项目根目录下运行pip install -e .。这会在你的虚拟环境中创建一个指向当前目录的“链接”使得my_project就像一个已安装的包一样可以被任何地方导入。这是管理Python项目依赖和测试的标准方式。你需要一个setup.py或pyproject.toml文件来定义项目。临时方案修改sys.path就像我们之前在测试文件开头做的那样。缺点是每个测试文件都要加且容易出错。设置PYTHONPATH环境变量在运行pytest前设置export PYTHONPATHsrc:$PYTHONPATHLinux/macOS或set PYTHONPATHsrc;%PYTHONPATH%Windows。也可以配置在IDE的运行设置里。7.2 测试隔离与状态污染问题一个测试的修改影响了另一个测试的结果导致测试有时成功有时失败随机失败。根本原因测试之间共享了可变状态。比如使用了scope”module”或”session”的夹具但测试修改了夹具返回对象的内容且没有在yield后进行清理。解决策略优先使用scope”function”为每个测试提供全新的、干净的环境。如果必须共享则只读或严格重置如果共享的夹具是只读的如配置字典、只读数据库连接问题不大。如果需要修改必须在夹具的清理部分yield之后或测试的teardown中将状态重置到已知的初始状态。使用pytest的内置夹具tmp_path和monkeypatchtmp_path为每个测试提供唯一的临时目录完美隔离文件操作。monkeypatch用于在测试中临时修改或替换全局变量、函数、类、环境变量等测试结束后会自动恢复。这是实现隔离的强力工具。def test_with_monkeypatch(monkeypatch): import os # 临时修改环境变量 monkeypatch.setenv(“MY_SETTING”, “test_value”) assert os.environ[“MY_SETTING”] “test_value” # 临时替换一个函数 def fake_get_user(): return “Mocked User” import my_project.user_service monkeypatch.setattr(my_project.user_service, “get_user_from_api”, fake_get_user) # 现在测试中调用的 get_user_from_api 会是我们的假函数7.3 测试太慢怎么办识别慢测试使用pytest --durations10命令它会列出运行时间最长的10个测试。使用pytest-xdist并行运行安装后使用pytest -n auto根据CPU核心数自动分配或pytest -n 4指定4个进程来并行运行测试。注意并行时测试必须完全独立不能有资源竞争。优化夹具作用域将昂贵的初始化如启动Docker容器、连接远程服务放到scope”session”的夹具中。Mock外部依赖对于网络请求、数据库查询等I/O操作使用pytest-mock或unittest.mock进行模拟返回预设的假数据避免真实的网络延迟。7.4 组织测试代码的最佳实践测试目录结构通常一个tests/目录对应一个src/目录。在tests/下可以按模块名创建对应的测试文件test_*.py也可以创建子目录如tests/unit/,tests/integration/,tests/api/来分类。conftest.py的放置conftest.py可以放在任何目录。该目录及其子目录中的测试都可以访问其中定义的夹具。你可以有多层conftest.py实现夹具的层级共享。测试函数命名除了以test_开头名字应该清晰描述测试的意图。好的命名如test_add_user_with_duplicate_username_raises_error差的命名如test_add_user_2。保持测试简单一个测试函数最好只测试一个具体的行为或场景。遵循“Arrange-Act-Assert”准备-执行-断言模式让测试逻辑清晰。不要在生产代码中引入测试逻辑避免为了测试方便而在业务代码里写if TESTING:这样的分支。应该使用Mock和夹具来隔离测试环境。7.5 调试失败的测试当测试失败时pytest提供了强大的调试工具pytest -v --tblong显示最详细的错误回溯信息。pytest --pdb在测试失败时自动进入Python调试器pdb。你可以检查当时的变量状态单步执行。在测试中使用print虽然听起来不高级但在复杂的数据流中插入print语句快速查看中间值往往是最高效的调试手段。pytest默认会捕获标准输出但你可以用-s选项来禁用捕获让print的内容直接显示在控制台pytest -s test_file.py。使用pytest的capsys夹具如果你需要测试或验证代码打印了什么可以使用capsys夹具来捕获输出。def test_output(capsys): print(“Hello, pytest!”) captured capsys.readouterr() assert captured.out “Hello, pytest!\n”