Pytest 是一个功能强大的 Python 测试框架,其中的Fixture 是 Pytest 中的一个重要功能。它允许你设置一些特定的测试环境或准备测试数据,这些环境和数据可以在多个测试用例中重复使用。通过使用fixture,你可以避免在每个测试函数中编写重复的设置和清理代码,使得测试更加干净、简洁,并提高代码的可维护性。
本文将介绍 Pytest Fixture 的概念、用途以及十种常见的使用方法,并提供相应的示例代码。
1、什么是 Fixture?
Fixture 是 Pytest 中用于提供测试环境的一种机制,它可以在测试函数执行前后进行一些准备工作和清理工作,如初始化数据库连接、创建临时文件等。Fixture 可以被多个测试用例共享使用,从而提高测试代码的复用性和可维护性。
2、Fixture用途
fixture的主要用途包括:
- 设置测试环境:例如,配置数据库连接、初始化外部服务等。
- 准备测试数据:提供测试所需的数据,如用户信息、产品列表等。
- 模拟外部依赖:当测试难以直接访问外部系统时,可以使用fixture来模拟这些系统的行为。
- 执行特定操作:在测试前后执行某些特定操作,如临时修改配置、记录日志等。
- 共享资源:在不同的测试用例之间共享资源,减少资源的创建和销毁开销。
3、10种常见用法及示例
基础使用
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3]
def test_example(sample_data):
assert sum(sample_data) == 6
带参数的fixture
import pytest
@pytest.fixture(params=[1, 2, 3])
def number(request):
return request.param
def test_number(number):
assert number in [1, 2, 3]
使用范围(scope)
在pytest中,fixture的作用域决定了测试夹具的生命周期以及它们能被哪些测试用例或测试类访问。以下是pytest中fixture的几种作用域及其用途:
- function:这是fixture的默认作用域。当不指定scope参数时,fixture会在每个测试函数执行前被调用,并在测试函数结束后清理。它适用于需要为每个测试准备和清理资源的场合。例如,打开和关闭数据库连接、初始化和释放内存空间等。
- class:当设置scope='class'时,fixture会在一个测试类开始前被调用一次,然后在整个类的所有测试方法运行完毕后被清理。这适用于整个测试类共享的准备工作,如创建共享的测试环境或对象。
- module:如果设置scope='module',则fixture会在整个模块的第一个测试开始前被调用,并在模块中的所有测试完成后被清理。这通常用于模块级别的资源管理,比如建立和断开与外部服务的连接。
- package/package.module:当设置为scope='package'或scope='package.module'时,fixture将在整个包或指定的包的模块中运行一次。这适用于跨模块或跨包共享的测试资源,例如配置全局服务或执行一次性的环境设置。
- session:通过scope='session'设置,fixture将在整个测试会话中只运行一次。这适用于开销较大,且所有测试用例都可以共享的准备步骤,如复杂的系统级设置或一次性的资源分配。
- instance:如果设置了scope='instance',则可以为每个测试实例创建一个fixture实例。这允许在不同的测试用例之间共享状态,而不需要在每个测试用例中重新准备。
- classinstance:通过scope='classinstance',可以为每个测试类创建一个fixture实例。与instance类似,但适用于在类的不同方法间共享状态。
- once:使用scope='once'时,fixture只会被调用一次,无论被多少个测试用例或测试类引用。这对于单例资源管理或确保某些操作只执行一次非常有用。
例如:
import pytest
@pytest.fixture(scope="class")
def class_fixture():
print("setup")
yield "data"
print("teardown")
def test_use_fixture(class_fixture):
assert class_fixture == "data"
通过合理选择不同的fixture作用域,可以有效地组织和管理测试代码,提高测试的效率和可维护性。
fixture的依赖
可以指定一个fixture依赖于其他fixture。
import pytest
@pytest.fixture
def db():
return "sqlite:///:memory:"
@pytest.fixture
def session(db):
return create_session(db)
def test_database(session):
assert isinstance(session, Session)
示例2:
import pytest
@pytest.fixture
def login():
user = User()
user.login()
yield user
user.logout()
@pytest.fixture
def profile(login):
return login.get_profile()
使用autouse自动应用
通过设置autouse=True,无需手动将fixture作为参数传递到测试用例中。
import pytest
@pytest.fixture(autouse=True)
def print_hello():
print("Hello, World!")
def test_example():
pass
使用request对象访问fixture
request对象可以用来访问调用的fixture及其参数。
import pytest
@pytest.fixture(params=[1, 2, 3])
def numbers(request):
return request.param * 2
def test_numbers(numbers):
assert numbers % 2 == 0
异常处理
可以对fixture中的异常进行处理。
import pytest
@pytest.fixture(autouse=True)
def exception_handler():
try:
yield "some setup code"
except Exception as e:
print(f"Handled exception: {e}")
raise e
def test_example():
raise ValueError("test error")
使用indirect间接引用
indirect 参数是 Pytest 中 Fixture 的一个高级用法,在pytest中,indirect参数用于间接引用fixture。
indirect=True 是 @pytest.mark.parametrize 装饰器的一个可选参数。当设置为 True 时,它告诉 pytest,对应的参数值不是一个直接的输入值,而是一个用于请求 fixture 的名称。这意味着,pytest 会查找一个与参数值同名的 fixture,并使用该 fixture 的返回值作为测试用例的参数。
当使用indirect时,它允许你通过一个fixture的名称来引用另一个fixture,而不是直接使用它的返回值。这在某些情况下非常有用,比如当你需要将一个fixture的返回值作为另一个fixture的输入。
使用方法
- 在测试函数的参数列表中指定需要间接引用的 Fixture 名称。
- 在 @pytest.mark.parametrize 装饰器中使用 indirect=True 参数来启用间接引用。
示例1:
import pytest
@pytest.fixture
def test_data(request):
# 这里只是一个简单的示例,你可以根据需要生成更复杂的测试数据
data = request.param
return data * 2
然后,我们编写一个测试用例,并使用 @pytest.mark.parametrize 装饰器来参数化它。注意,我们在 indirect=True 时传递 fixture 名称 test_data,而不是直接的测试数据值:
def test_example(test_data):
assert test_data > 0
最后,我们使用 @pytest.mark.parametrize 来指定测试数据的范围,并将 indirect 设置为 True:
@pytest.mark.parametrize("test_data", [1, 2, 3], indirect=True)
def test_example(test_data):
assert test_data > 0
在这个例子中,pytest 会为每一组测试数据(1, 2, 3)调用 test_data fixture,并将 fixture 的返回值(即数据的两倍)作为 test_example 测试用例的参数。因此,test_example 测试用例实际上会运行三次,每次使用不同的参数值(2, 4, 6)。
通过这种方式,你可以使用 fixtures 来生成复杂的测试数据,并通过 @pytest.mark.parametrize 和 indirect=True 来参数化你的测试用例。
示例2:indirect间接引用fixture另外一种用法:
import pytest
# 定义一个fixture,返回一个字符串
@pytest.fixture
def string_fixture():
return "Hello, World!"
# 定义另一个fixture,接受一个字符串作为参数,并返回其长度
@pytest.fixture
def length_fixture(request):
string = request.getfixturevalue("string_fixture")
return len(string)
# 使用indirect间接引用length_fixture,并将结果传递给test_example测试函数
def test_example(length_fixture):
assert length_fixture == 13
在上面的示例中,我们定义了两个fixture:string_fixture和length_fixture。string_fixture返回一个字符串,而length_fixture接受一个字符串作为参数,并返回其长度。
在test_example测试函数中,我们使用indirect间接引用了length_fixture,并将其结果传递给测试函数。这样,pytest会自动解析length_fixture的依赖关系,并获取string_fixture的返回值作为输入。
运行上述代码,将会执行test_example测试函数,并断言字符串的长度是否为13。由于使用了indirect间接引用,我们可以灵活地管理fixture之间的依赖关系,并在测试中使用它们的结果。
使用fixtures获取所有fixtures
可以获取当前测试用例的所有fixtures。
import pytest
@pytest.fixture(scope="module")
def module_fixture():
return "module data"
def test_example(module_fixture, request):
fixtures = request.getfixturevalue("module_fixture")
assert fixtures == "module data"
自定义fixture解析器
可以自定义解析器来控制如何解析fixture的名称。
import pytest
from pytest_mock import MockerFixture
@pytest.fixture(scope="module", autouse=True)
def my_custom_parser(request, mocker: MockerFixture):
mocker.patch("my_module.some_function", return_value="mocked value")
request.addfinalizer(lambda: mocker.stop())
以上是pytest fixture的10种常见用法及示例,它们涵盖了从基本使用到高级技巧的各个方面。掌握这些用法可以帮助你编写更加高效和易于维护的测试代码。