Pytest 初学者教程:夹具、参数化和真实示例

  1. 首页
  2. 博客
  3. 测试自动化

测试自动化

Pytest 初学者教程:夹具、参数化和真实示例

Pytest 允许您使用简单函数和简单断言语句编写 Python 测试。逐步构建一个小型测试套件,阅读真实的故障输出并了解您每天都会使用的功能。

阅读约 10 分钟
面向初学者的 Pytest 教程:固定装置、参数化和真实示例

要点速览

  • Pytest 查找名为 test_* 的函数并使用简单的 assert 语句检查它们。
  • Fixtures 提供可重用的设置和清理,只需将它们命名为参数即可请求。
  • @pytest.mark.parametrize 运行一个包含多个输入的测试。
  • 将共享灯具放入conftest.py中,无需导入。

如果您因为感觉像一件苦差事而避免编写测试,那么这个 pytest 教程 应该会改变您的想法。 Pytest 删除了大部分仪式:没有要继承的类,也没有需要记住的特殊断言方法。一个测试可以只有两行。

我们将构建一个小型购物车并逐步进行测试。我们使用 pytest 9.2 在 Python 3.13 上运行了所有内容,显示的输出是真实的,尽管我们修剪了在您的计算机上不同的文件路径和计时细节。

为什么要进行pytest?

Python 附带了 unittest,它运行良好,但 pytest 通常是更方便的选择。这是每个中相同的检查:

任务 单元测试 py测试
检查相等性 self.assertEqual(a, b) 断言 a == b
检查异常 self.assertRaises(...) pytest.raises(...)
测试结构 继承自TestCase的类 一个简单的函数
设置和清理 setUp和tearDown方法 可重复使用的固定装置
许多投入 subTest 循环 参数化

Pytest 还可以运行您现有的 unittest 测试,因此您可以逐渐切换。它还有一个大型插件生态系统,包括用于覆盖的 pytest-cov 和用于并行运行的 pytest-xdist。

安装pytest

首先创建一个虚拟环境,如我们的Python虚拟环境教程中所述,然后安装它:

pip install pytest
pytest --version

# pytest 9.2.0

创建一个包含两个文件 cart.py 和 test_cart.py 的项目文件夹。 Pytest 通过以下命名规则自动查找测试:名为 test_*.py 或 *_test.py 的文件、以 test 开头的函数以及以 Test 开头的类。如果没有进行测试,首先要检查命名不匹配。

要测试的代码

# cart.py
class Cart:
    def __init__(self):
        self.items = []

    def add(self, name, price, qty=1):
        if qty < 1:
            raise ValueError("qty must be at least 1")
        self.items.append((name, price, qty))

    def total(self, discount=0):
        subtotal = sum(price * qty for _, price, qty in self.items)
        return round(subtotal * (1 - discount / 100), 2)

您的第一次测试

# test_cart.py
from cart import Cart

def test_empty_cart_is_free():
    assert Cart().total() == 0

这是一个完整的测试。从项目文件夹运行 pytest,它会报告一项通过的测试。如果出现导入​​错误,请按 python -m pytest 运行,这会将当前文件夹添加到导入路径。

阅读失败

这就是 pytest 真正闪光的地方。这是一个故意错误期望值的测试:

def test_wrong_total():
    c = Cart()
    c.add("Keyboard", 1500)
    assert c.total() == 1400

这是我们运行时打印的内容:

    def test_wrong_total():
        c = Cart()
        c.add("Keyboard", 1500)
>       assert c.total() == 1400
E       assert 1500.0 == 1400
E        +  where 1500.0 = total()

FAILED fail_example.py::test_wrong_total - assert 1500.0 == 1400
============================== 1 failed in 0.12s ==============================

> 标记失败行,E 行显示实际值。您不必添加自定义消息,因为 pytest 重写了 assert 语句,以便它们可以报告比较的内容。它对列表和字典执行相同的操作,准确指出哪些项目不同。

测试异常

错误处理也是行为,值得测试。 pytest.raises 仅当块引发预期异常时才通过:

import pytest

def test_rejects_zero_qty():
    with pytest.raises(ValueError, match="at least 1"):
        Cart().add("Pen", 10, qty=0)

可选的 match 参数使用正则表达式检查消息,因此不同的 ValueError 不会意外通过。要了解如何引发和处理此类错误,请阅读我们的 Python 异常处理指南。

夹具:可重复使用的设置

大多数测试都需要先准备一些东西。 fixture 是提供它的函数。用 @pytest.fixture 装饰它,任何将其命名为参数的测试都会收到结果:

@pytest.fixture
def cart():
    c = Cart()
    c.add("Keyboard", 1500)
    c.add("Mouse", 500, qty=2)
    return c

def test_total(cart):
    assert cart.total() == 2500

默认情况下,每个测试都会获得一个新的购物车,因此测试不会相互影响。 Pytest 将参数名称与固定装置的名称相匹配,并且您永远不会自己调用固定装置。 @pytest.fixture是一个装饰器,我们在Python装饰器解释中对其进行了解释。

清理产量

当您事后需要清理时,请使用 yield 而不是 return。 yield 之后的代码在测试完成时运行,即使测试失败:

@pytest.fixture
def temp_db():
    print("\nopen db")
    yield {"users": []}
    print("close db")

def test_db(temp_db):
    assert temp_db["users"] == []

使用 -s 运行以显示打印输出,确认订单已打开、测试,然后关闭。

夹具范围和conftest.py

默认情况下,夹具每次测试运行一次。对于昂贵的设置,例如数据库或浏览器,请使用 scope 更改其生命周期:

适用范围 每创建一次
function(默认) 测试
类 测试班
模块 文件
包 套餐
会话 全程试运行

将多个文件使用的灯具放入名为 conftest.py 的文件中。 Pytest 自动加载它,因此测试可以使用它的装置而无需导入。不过,要小心共享范围,因为一个测试所做的更改可能会泄漏到下一个测试中。

参数化:一项测试,多项输入

不要为每个值复制测试,而是列出案例:

@pytest.mark.parametrize("discount, expected", [
    (0, 2500),
    (10, 2250),
    (100, 0),
])
def test_discount(cart, discount, expected):
    assert cart.total(discount) == expected

每行都成为自己的测试,您可以在我们运行完整文件的详细输出中看到这一点:

test_cart.py::test_total PASSED                        [ 16%]
test_cart.py::test_discount[0-2500] PASSED             [ 33%]
test_cart.py::test_discount[10-2250] PASSED            [ 50%]
test_cart.py::test_discount[100-0] PASSED              [ 66%]
test_cart.py::test_rejects_zero_qty PASSED             [ 83%]
test_cart.py::test_empty_cart_is_free PASSED           [100%]

============================== 6 passed in 0.05s ==============================

如果一行失败,其他行仍然运行,并且名称准确地告诉您哪些值导致了问题。请注意,我们将参数化与 cart 夹具结合起来。要选择可能发现错误的值,请使用边界值分析。

提示:当您的参数很长或不清楚时,请通过ids=["none", "ten", "full"]在报告中为每个案例提供一个可读的名称。

有用的内置装置

Pytest 包括几个现成的装置。这两个特别方便,并且都在我们的运行中通过了:

def test_write_report(tmp_path):
    report = tmp_path / "report.txt"
    report.write_text("passed")
    assert report.read_text() == "passed"

def read_mode():
    return os.environ.get("APP_MODE", "dev")

def test_mode(monkeypatch):
    monkeypatch.setenv("APP_MODE", "staging")
    assert read_mode() == "staging"

tmp_path 为每个测试提供一个唯一的临时文件夹,因此测试永远不会触及真实文件。 monkeypatch 更改环境变量或属性,并在测试结束时撤消更改。另一个有用的工具是 capsys,它捕获打印输出,以便您可以检查它。

标记:skip 和 xfail

标记为测试添加标签和行为:

@pytest.mark.skip(reason="payment sandbox is down")
def test_payment():
    assert False

@pytest.mark.xfail(reason="floating point")
def test_rounding():
    assert 0.1 + 0.2 == 0.3

skip 根本不运行测试。 xfail 运行它但预计会失败,因此已知问题不会破坏您的构建。与同一文件中的其他额外测试一起,我们的运行总结为 3 passed, 1 skipped, 1 xfailed。顺便说一句,舍入测试确实失败了:在浮点算术中,0.1 + 0.2 并不完全是 0.3。比较小数时使用 pytest.approx(0.3)。

您还可以创建自己的标记,例如 @pytest.mark.slow,并仅运行带有 pytest -m slow 的标记,或者使用 -m "not slow" 忽略它们。在配置文件中注册自定义标记以避免警告。

有用的命令行标志

命令 它的作用
pytest -v 显示每个测试名称
pytest-q 显示较短的摘要
pytest-x 在第一次失败时停止
pytest -k 折扣 运行名称与单词匹配的测试
pytest-s 显示 print 输出
pytest--lf 仅重新运行最后的失败
pytest--pdb 失败时打开调试器
pytest test_cart.py::test_total 运行一项特定测试

在 CI 中运行 pytest

当测试在每次更改时自动运行时,测试才最有价值。一个最小的 GitHub Actions 作业如下所示:

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-python@v5
    with:
      python-version: "3.13"
  - run: pip install -r requirements.txt
  - run: python -m pytest

当测试失败时,Pytest 会以非零代码退出,这就是管道停止的原因。我们没有执行此工作流程文件,因此请根据您的项目进行调整。我们的 GitHub Actions CI/CD 指南 解释了详细信息。 Pytest 也适用于浏览器测试,如我们的 Selenium Python 教程 和 页面对象模型教程 所示。

最后的话

您现在拥有大多数 Python 测试套件所需的东西:简单的断言、设置固定装置、变化参数化和一些速度标志。从小处开始。选择一个您最近修复了错误的函数,然后首先针对该错误编写测试。如果您不确定在每个级别测试什么,请阅读单元测试与集成测试。

常见问题

pytest 是用来做什么的?

Pytest 是一个 Python 测试框架。它用于单元、集成和 API 测试,并且还可以与 Selenium 和 Playwright 等浏览器工具配合良好。

pytest 比单元测试好吗?

许多团队更喜欢它,因为它具有更简单的语法、固定装置和更清晰的故障。 unittest 不需要安装,并且 pytest 也可以运行现有的 unittest 测试。

pytest中的fixture是什么?

夹具是一种准备测试所需的功能,例如数据或连接。测试通过将其命名为参数来接收它。

什么是conftest.py?

它是一个特殊文件,用于跨测试文件共享固定装置和挂钩。 Pytest 自动加载它,因此它的装置不​​需要 import 语句。

为什么 pytest 找不到我的测试?

通常这是一个命名问题。文件应命名为 test_*.py 或 *_test.py,函数应以 test 开头。还要检查您是否从正确的文件夹运行它。

继续学习

与Stack Quora合作

需要测试、WordPress 或 SEO 方面的帮助?

我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。

继续阅读

更多指南 测试、人工智能和现代开发

喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。

人工智能

人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它

人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。

阅读文章 →
DevOps

环境变量解释:.env 文件、秘密和配置正确完成

使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。

阅读文章 →
软件开发

API 速率限制解释:算法、标头和处理 429 错误

API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。

阅读文章 →
测试自动化

Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误

使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。

阅读文章 →
软件测试

代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义

用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker Volumes 与 Bind Mounts:以正确的方式保留容器数据

Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。

阅读文章 →
测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。

阅读文章 →
软件测试

用户验收测试 (UAT):流程、清单和真实示例

用户验收测试解释:谁运行它、它与 QA 有何不同、七步流程、UAT 测试用例模板、清单、签核标准和常见错误。

阅读文章 →
数据库与 SQL

SQL NULL 值解释:IS NULL、COALESCE、NULLIF 和要避免的陷阱

通过我们运行的查询解释了 SQL NULL 值:三值逻辑、IS NULL、COALESCE、NULLIF,聚合、连接和排序如何处理 NULL,以及它悄悄引起的错误。

阅读文章 →
数据库与 SQL

SQL CASE WHEN 语句:语法、实例和常见错误

SQL CASE WHEN 解释了我们运行的查询:简单和搜索表单、条件顺序、SUM 透视、COUNT 技巧、自定义排序、更新和要避免的 NULL 陷阱。

阅读文章 →
软件开发

什么是智威汤逊?通过工作示例解释 JSON Web 令牌

什么是智威汤逊? JSON Web Tokens 解释:这三个部分,在我们运行的代码中构建和篡改的令牌,JWT 与会话,存储,刷新令牌和安全错误。

阅读文章 →
软件开发

JavaScript 事件循环解释:调用堆栈、微任务和计时器以及示例

JavaScript 事件循环用我们运行的代码进行了解释:调用堆栈、任务和微任务队列、为什么 setTimeout 0 不是立即执行、阻塞、饥饿和面试测验。

阅读文章 →
软件开发

Python 生成器和 Yield 解释:通过真实示例节省内存

Python 生成器和yield 用我们运行的代码进行了解释:它们如何暂停和恢复、测量内存节省、管道、无限序列、yield from、发送和常见陷阱。

阅读文章 →
人工智能

什么是嵌入?向量和余弦相似度简单解释

什么是嵌入?关于向量、余弦相似度和语义搜索的简单语言指南,其中包含我们运行的一个小示例、常见用途、选择模型和已知限制。

阅读文章 →
DevOps

蓝绿部署与金丝雀部署:差异、权衡以及如何选择

蓝绿部署与金丝雀部署解释:每种部署的工作原理、成本、风险和回滚、我们测试的稳定用户分桶、数据库迁移、滚动更新以及如何选择。

阅读文章 →
测试自动化

不稳定的测试:造成这些问题的原因以及如何彻底解决它们

我们运行的演示解释了不稳定的测试:主要原因、如何确认和诊断、正确的时间修复、共享状态和日期以及隔离过程。

阅读文章 →
软件测试

负载测试与压力测试:差异、指标和 k6 示例

负载测试与压力测试的解释:各自证明什么、峰值和浸泡测试、重要的指标、为什么平均值会误导、k6 个示例以及如何计划测试。

阅读文章 →
软件测试

测试用例与测试场景:主要区别、示例以及何时使用它们

测试用例与测试场景的解释:清晰的定义、并排的表格、从场景到案例的密码重置功能、模板以及何时使用每个功能。

阅读文章 →
数据库与 SQL

SQL 查询优化:读取执行计划并修复慢速查询(带计时)

对一百万行进行 SQL 查询优化测试:读取执行计划、添加正确的索引、修复不可控制的过滤器、使用复合索引和键集分页。

阅读文章 →
数据库与 SQL

SQL UNION 与 UNION ALL:差异、示例和性能测试

SQL UNION 与 UNION ALL 通过我们运行的查询进行了解释:重复、都遵循的规则、真实的数据错误、测量的性能、INTERSECT 和 EXCEPT 以及何时使用它们。

阅读文章 →
软件开发

JavaScript Fetch API 教程:GET、POST、错误处理和超时

针对本地服务器测试的 JavaScript Fetch API 教程:GET 和 POST、为什么不抛出 404、可重用的帮助程序、查询字符串、超时、CORS 和常见错误。

阅读文章 →
软件开发

Python 文件处理:读取、写入、CSV、JSON 和 pathlib 以及示例

使用我们运行的代码解释了 Python 文件处理:打开模式、使用、读取大文件、CSV、JSON、pathlib、二进制文件、安全写入、错误处理和常见错误。

阅读文章 →
软件开发

Python 类和对象:带有实际示例的 OOP 教程

用我们运行的代码解释 Python 类和对象:__init__、方法、类与实例属性、继承、属性、dunder 方法、数据类和常见错误。

阅读文章 →
人工智能

什么是大语言模型?法学硕士如何运作,在没有炒作的情况下进行解释

什么是大语言模型?法学硕士用简单的语言解释:标记、嵌入、注意力、训练、温度和上下文窗口、一个微小的下一个单词演示和真正的限制。

阅读文章 →
DevOps

适合初学者的 Linux 命令:带有示例的基本终端指南

适合初学者的 Linux 命令,按任务分组:导航、文件、查看日志、grep、管道、权限、进程和档案,并附有实用的日志分析示例。

阅读文章 →
测试自动化

Selenium 与 Playwright:主要区别、代码比较以及选择

Selenium 与 Playwright 比较:架构、等待、浏览器和语言支持、调试、两种工具中的相同测试、迁移建议以及如何选择。

阅读文章 →
软件测试

等价划分:通过实例解释测试设计技术

等效分区解释:如何找到有效和无效分区,年龄、密码和优惠券的工作示例,我们运行的测试,以及为什么将其与边界配对。

阅读文章 →
软件测试

软件测试中的严重性与优先级:差异、矩阵和实例

软件测试中的严重性与优先级解释:清晰的定义、带有示例的四向矩阵、级别尺度、谁设置每个级别以及如何运行错误分类。

阅读文章 →
数据库与 SQL

SQL 子查询教程:类型、相关子查询和 EXISTS 以及示例

SQL 子查询教程,其中包含我们运行的查询:标量、IN、FROM 和 SELECT 子查询、相关子查询、EXISTS、NOT IN NULL 陷阱以及子查询与 JOIN 与 CTE。

阅读文章 →
数据库与 SQL

SQL 事务和 ACID 解释:COMMIT、ROLLBACK 和隔离级别

SQL 事务和 ACID 通过我们运行的银行转账进行了解释:COMMIT、ROLLBACK、保存点、锁、隔离级别、脏读、死锁和最佳实践。

阅读文章 →
软件开发

Git 命令备忘单:您实际使用的命令,以及示例

Git 命令备忘单按任务分组:设置、暂存、分支、远程、存储、历史记录和撤消错误,以及来自真实演示存储库的输出。

阅读文章 →
软件开发

JavaScript Promise 解释:then、catch、Promise.all 和常见错误

JavaScript 承诺用我们运行的代码进行了解释:状态、then 和 catch 链、错误处理、Promise.all、allSettled、race 和 any、事件循环和常见错误。

阅读文章 →
软件开发

Python 字典教程:方法、示例和常见模式

Python 字典教程,包含我们运行的代码:创建、读取和更新字典、关键方法、合并、理解、计数和分组模式以及常见错误。

阅读文章 →
人工智能

什么是 RAG?通过工作示例解释检索增强生成

什么是RAG?检索增强生成逐步解释:索引、分块、嵌入、检索、一个小型的 Python 工作示例、评估和常见故障。

阅读文章 →
DevOps

Terraform 初学者教程:从第一个文件到模块的基础设施即代码

适合初学者的 Terraform 教程:从没有云帐户开始,学习初始化、计划和应用,然后是变量、输出、状态、模块和安全的团队工作流程。

阅读文章 →
DevOps

Docker 与 Kubernetes:主要区别、它们如何协同工作以及您需要哪些

Docker 与 Kubernetes 的解释:各自的作用、同一个应用程序同时运行、Docker Compose 和 Swarm 的比较、dockershim 的变化以及如何决定您需要什么。

阅读文章 →
测试自动化

Pytest 初学者教程:夹具、参数化和真实示例

面向初学者的 pytest 教程,其中包含我们在 pytest 9.2 上运行的测试:断言、固定装置、参数化、conftest、标记、tmp_path、monkeypatch、有用的标志和 CI。

阅读文章 →
软件测试

软件测试生命周期 (STLC):6 个阶段、进入和退出标准、示例

软件测试生命周期解释:六个 STLC 阶段,包括进入和退出标准、可交付成果、有效的登录功能示例、STLC 与 SDLC 以及敏捷中的 STLC。

阅读文章 →
软件开发

Git merge 与 rebase 的区别:示例及如何选择

用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。

阅读文章 →
软件开发

Python 列表推导式:语法、示例与何时避免使用

Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →