Pytest Fixtures是Pytest框架中用于管理测试前置条件和后置清理的一种强大工具。可以帮助我们在测试中设置初始状态,减少重复代码,并提高测试的可维护性
下面我们来学习一下如何使用它
前置条件
- 已经安装了最新版本 已安装 Python
- 基本了解如何使用 Pytest
第 1 步 — 设置项目目录
使用 idea 创建一个项目并设置一个虚拟环境,以确保项目依赖项得到隔离和管理有效
首先创建一个目录,该目录将包含我们在此编写的代码教程:
之后,我们在终端查看是否已经激活虚拟环境,在本例中为:venv
接下来,使用以下代码创建一个文件:app.py
Library
类管理一系列书籍,它从一个空列表开始,并具有添加、检索、更新和列出书籍的方法
add_book()
方法添加一本书及其标题和作者
get_book()
方法按索引检索一本书
update_book()
方法根据书籍的索引更新书籍的详细信息
list_books()
方法列出图书馆中的所有书籍,而 clear_books()
方法清理列表
要为此类编写测试,请先安装 Pytest:
创建一个 tests
目录来保存测试文件,这个目录直接创建为 Python包
之后,我们直接使用PyCharm。运行下面的测试脚本
使用以下命令运行测试:
输出将如下所示:
虽然测试结果是正确的,但测试本身是有问题的。主要是Library
类的实例中有重复的,如果 Library
类需要新的实例化参数,则需要更新所有测试以包含新参数
为避免这种重复,我们可以使用固定装置
第 2 步 — 开始使用 Pytest Fixture
要了解如何使用 Pytest Fixture,让我们看一下四步过程 对于编写测试用例:
- 安排:通过设置对象、服务、 数据库记录、URL、凭据或测试所需的任何条件
- 操作:执行要测试的操作,例如调用函数
- 断言:验证操作的结果是否符合您的预期
- 清理:恢复环境,避免影响其他测试
Pytest Fixture在 安排 步骤中发挥作用。这些固定装置是以字符串、数字、字典或对象的形式返回数据的函数,测试函数或方法可以使用这些数据。你可以告诉Pytest,函数是带有@pytest.fixture
装饰器的固定装置
可以像下面这样:
numbers
固定装置返回一个列表 [1, 2, 3]
,该列表在 test_sum_numbers
函数中用于验证列表的总和
我们可以通过定义两个fixtures
将相同的概念应用于程序,创建返回 Library()
实例和包含书籍的实例的固定装置
复制test_library.py
文件命名为test_library_fixture.py
方便区分
现在,每个测试用例都使用这些固定装置来获取必要的Library
实例了
使用夹具的好处包括提高代码的可读性和可维护性,因为设置逻辑是集中的,并且可以在多个测试中重复使用
此外,fixtures
将测试设置和执行明确分离,使测试更易于理解和修改
这种方法还允许更灵活和模块化的测试,因为可以使用不同的fixtures
轻松配置和测试不同的初始状态
保存新更改并运行测试:
这样一来,测试就通过了。现在我们应该已经基本了解了fixtures
的工作原理以及它们带来的好处,然后我们可以继续下一部分,从其他fixtures
请求fixtures
第 3 步 — 从其他固定装置请求固定装置
Pytest 中的fixtures
是模块化的,允许一个fixtures
使用另一个fixtures
例如,可以重写library_with_books
以调用library()
,如下所示:
现在我们再次运行测试:
将会看到测试顺利通过:
在其他fixtures
中使用fixtures
是一个很有用的功能。但是,重要的是要谨慎对待fixtures
依赖性,确保基础装置(library
)中的更改不会无意中影响从属fixtures
。适当的状态管理对于避免测试剥落至关重要,确保每个测试都具有干净且独立的状态,以保持可靠和可预测的测试结果。
第 4 步 — 在多个文件中使用fixtures
和conftest.py
文件
随着项目的发展,使用相同fixtures
的多个测试文件是很常见的。在每个测试文件中设置fixtures
效率不高。一个好的解决方案是创建一个 conftest.py
文件并在其中添加fixtures
。Pytest 将自动发现这些fixtures
,并将它们用于所有测试文件,而无需导入它们
为此,我们可以在tests
目录中的 conftest.py
文件内定义固定装置,如下所示:
一旦定义了fixtures
,所有测试文件,包括子目录中的文件,都将能够使用fixtures
。
接下来,我们将所有的导入和固定装置移动到 conftest.py
文件中,如下所示:
通过该更改,tests/test_library_fixture.py
文件仅包含测试函数
保存更改后,再次运行测试。即使测试文件没有任何fixtures
导入,用例也可以正常运行:
输出:
使用 conftest.py
时,请确保所有测试文件都可以访问相同的fixtures
,而无需冗余导入。
第 5 步 — 了解 Pytest fixtures
范围
在 Pytest 中,fixtures
是设置测试所需资源的强大方法。 fixtures
的一个关键特征是它们的范围,它决定了fixtures
的长度 将处于活动状态以及何时设置和拆除。
以下是从最低到最高范围排序的范围:
- 功能范围:默认范围。
fixtures
在设置之前 每个测试功能并在测试功能完成后拆除。 - 类范围:该
fixtures
为每个测试类别设置一次,可用于 该类中的所有测试方法。 - 模块范围:该
fixtures
为每个测试模块设置一次,并且可用 到该模块中的所有测试功能。 - 包装范围:该
fixtures
为每个包装设置一次,可用于 该包中的所有测试。 - 会话范围:该
fixtures
在整个测试会话中设置一次,并且是 可用于在该会话期间运行的所有测试
首先,打开文件:conftest.py
用单个固定装置替换文件的内容:conftest.py
并注销其他脚本
接下来,打开测试文件:
清除所有内容并添加以下修改后的测试以使用单个fixtures
:
这里的主要变化是test_add_book
使用library
固定装置。为了确保正确添加书籍,测试会检查现有书籍和新添加的书籍是否存在
若要确保测试运行正确,请再次运行测试脚本:
输出应如下所示:
但是,有时候我们不希望所有测试都在一个文件中,因为很难理解作用域之间的差异,特别是当涉及到module
、 package
或session
作用域时。因此,我们将测试文件分成两个文件
首先,创建一个测试文件,该文件将只有两种方法用于测试是否可以添加和检索书籍:
将以下代码添加到该文件中:
接下来,创建第二个测试文件,用于测试更新书籍并列出所有书籍:
删除文件:test_library.py
为确保一切运行正常,请重新运行 Pytest 命令:
输出应如下所示:
功能范围 Function scope
function scope
是默认范围。这意味着为每个测试功能设置fixtures
并在每次测试后销毁。因此,我们见过的任何示例都使用了function
作用域。这确保了每个测试都具有干净的状态
使用--setup-show
标志再次运行测试,以观察设置和拆卸过程:
输出如下所示:
SETUP F library
和TEARDOWN F library
行表明fixtures
在每次测试之前设置并在每次测试后拆除,确保每次测试的新鲜状态。这里, F
代表function
作用域,这是Pytest中的默认作用域。这意味着fixtures
针对每个测试功能单独应用
这种方法确保测试之间的隔离,防止共享状态带来的副作用。但是,如果我们要设置数据库连接等资源,则成本可能会很高,因为没有真正的理由为每个测试持续创建新的连接
模块范围 Module scope
Python 中的模块是可以使用import
关键字导入的单个文件。 Pytest 允许我们的固定装置定义模块范围 。 module
范围允许模块内的所有测试共享固定装置,从而确保一致的状态。这意味着该装置实例化一次,并在模块中测试代码的整个执行过程中持续存在。当最后一个测试执行时,fixtures
将被销毁
此范围通常适用于以下场景:
- 设置或拆卸过程涉及资源密集型操作,例如数据库连接或加载特定于模块的配置
- 性能得到提高,因为它减少了重复设置和拆卸,因为实例在需要共享
fixtures
的所有测试中都是重复使用的
但是,如果我们的测试修改了fixtures
,则可能会导致测试之间的不一致。因此,只有当我们确定状态保持不变,或者如果已修改,其他测试可以处理它时,才应使用此方法
要了解模块作用域如何处理多个文件,现在我们打开 :conftest.py
并将范围设置为:module
现在使用以下命令运行测试:
输出将显示以下内容:
在输出中,我们可以看到对于每个模块(每个测试文件)中的所有测试,设置和拆卸都用SETUP M library
和TEARDOWN M library
包装。这意味着夹具在模块中的任何测试运行之前设置一次,并在模块中的所有测试完成后拆除
这减少了为每个测试创建和拆除库的开销,提供更高效的设置,同时仍然确保不同测试文件之间的隔离
但是,由于fixtures
在模块内共享,因此在一次测试中对其状态所做的更改可能会影响后续测试。这就是为什么某些测试可能会失败的原因,因为它们假设fixtures
以干净状态启动,但事实并非如此。
包装范围 Package scope
package
范围对于在同一包内的多个模块之间共享固定装置非常有用。 package
范围固定装置在包的开头(包含__init__.py
文件的目录)创建,并在该包内的所有子目录(包)之间共享。然后,在封装中最后一次测试的拆卸过程中,fixtures
被破坏掉
若要应用范围,请更新文件:
现在使用以下命令重新运行测试:
输出将显示:
在此输出中,我们可以看到fixtures
在包的开头设置一次( SETUP P library
)并在末尾拆除( TEARDOWN P library
)。因此,一旦test_update_book
fixtures
进行更新,所有其他测试都会失败,因为它们的编写方式期望fixtures
是一个干净的,但事实并非如此
就像模块作用域一样,使用包作用域时确保测试不会修改固定装置非常重要;否则,后续测试可能会因状态变化而失败
会话范围 Session scope
session
范围是另一个范围,当我们希望函数共享相同的测试设置时,它会很有帮助。 session
范围固定装置在测试运行开始时创建,并在所有测试完成后销毁。它们在整个测试会话中持续存在,这使得它们对于为整个测试会话设置昂贵的资源(例如数据库连接)的装置很有用
将范围设置为:session
使用以下命令运行测试:
在会话范围内, library
装置在整个测试会话开始时设置一次,并在所有测试完成后拆除
此范围对于最小化设置和拆卸操作最为有效,因为它在整个测试会话中仅执行一次这些操作,无论涉及多少测试包或模块
第 6 步 — 参数化fixtures
在Python中,使用fixtures
的函数可以被参数化以编写简洁和 可读的测试。使用这些装置的测试函数被多次调用, 每次都使用不同的参数执行
当处理各种数据库连接值、多个文件等情况下,这种方法是有益的
要使用参数化,需要将params
关键字参数传递给带有一组输入值的固定装置装饰器,如下所示: @pytest.fixture(params=[values...])
要了解其工作原理,请创建一个文件:test_parametrization.py
添加以下代码,该代码使用 Pytest 的夹具参数化:
original_file_path()
装置提供params
列表中的各种文件路径。 test_convert_to_hyphens()
测试依赖于此固定装置并运行多次,针对列表中的每个文件路径运行一次。这确保了对convert_to_hyphens()
函数的全面测试,该函数用连字符替换下划线
要执行测试,请运行:
输出将显示类似于以下内容的输出:
此输出显示测试函数运行了四次,每次都具有不同 输入。
有了这个,我们就可以有效地参数化fixtures
第 7 步 — 使用内置Fixtures
Pytest 附带涵盖常见测试场景的内置固定装置。这些装置通过减少样板代码并通过标准功能确保一致性来简化测试的编写和维护
- monkeypatch: 临时修改函数、类、字典等。
- Request: 提供有关请求夹具的测试功能的信息。
- TMPDIR: 返回对每个测试函数唯一的临时目录路径对象。
- tmp_path_factory: 返回公共基本临时目录下的临时目录。
- recwarn: 记录测试函数发出的警告。
- Capsys: 捕获对 sys.stdout 和 sys.stderr 的写入
在该步骤让我们看一下tmp_path
Fixtures
。此装置对于管理临时目录至关重要,我们可以避免使用真实目录,由于各种平台(Windows、macOS、Unix)上的文件路径不同,真实目录可能会很复杂。tmp_path
固定装置提供了一种受控、隔离且与平台无关的目录管理方法,具有自动设置和拆卸功能
首先我们来创建一个文件:test_builtin_fixtures.py
然后添加以下代码:
此代码使用tmp_path
创建一个临时目录和文件。 它验证文件的存在和内容,确保测试 环境在不同平台上是隔离且一致的
运行该文件:
输出如下所示:
如果不使用tmp_path
,测试会很复杂:
此代码不仅难阅读,而且冗长。使用Fixtures
可以简化这一点,并使测试更具可读性和可维护性
如果内置的固定装置还不够,Pytest 还有很广泛的第三方插件列表。其中一些插件提供了有用的Fixtures
。使用它们可以直接 pip
进行安装
相信我如果你能够看完这篇文章并进行实际脚本的编写一定能够对你有所帮助!