Pytest Fixtures 和常规函数都可用于构建测试代码并减少代码重复。 George Udosen 提供的答案很好地解释了这一点。
但是,OP 专门询问了 pytest.fixture 和常规 Python 函数之间的区别,并且存在许多区别:
Pytest Fixtures 的作用域
默认情况下,pytest.fixture 会为每个引用夹具的测试函数执行。但是,在某些情况下,夹具设置可能在计算上很昂贵或很耗时,例如初始化数据库。为此,pytest.fixture 可以配置为更大的scope。这允许 pytest.fixture 在模块中的测试(module 范围)甚至在 pytest 运行的所有测试(session 范围)中重用。以下示例使用模块范围的固定装置来加速测试:
from time import sleep
import pytest
@pytest.fixture(scope="module")
def expensive_setup():
return sleep(10)
def test_a(expensive_setup):
pass # expensive_setup is instantiated for this test
def test_b(expensive_setup):
pass # Reuses expensive_setup, no need to wait 10s
虽然可以通过常规函数调用来实现不同的作用域,但作用域的固定装置使用起来更愉快。
Pytest Fixtures 基于依赖注入
Pytest 在测试收集阶段注册所有夹具。当测试函数需要一个名称与注册的夹具名称匹配的参数时,Pytest 将注意为测试实例化夹具并将实例提供给测试函数。这是dependency injection.的一种形式
与常规函数相比,优势在于您可以按名称引用任何pytest.fixture,而无需显式导入它。例如,Pytest 带有一个 tmp_path 固定装置,任何测试都可以使用它来处理临时文件。以下示例取自Pytest documentation:
CONTENT = "content"
def test_create_file(tmp_path):
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.txt"
p.write_text(CONTENT)
assert p.read_text() == CONTENT
assert len(list(tmp_path.iterdir())) == 1
assert 0
用户在使用前不必导入tmp_path,非常方便。
甚至可以在没有测试功能请求的情况下将夹具应用于测试功能(请参阅Autouse fixtures)。
Pytest 固定装置可以参数化
很像test parametrization, fixture parametrization 允许用户指定夹具的多个“变体”,每个“变体”具有不同的返回值。使用该夹具的每个测试都将执行多次,每个变体一次。假设您想测试您的所有代码是否都针对 HTTP 和 HTTPS URL 进行了测试,您可能会执行以下操作:
import pytest
@pytest.fixture(params=["http", "https"])
def url_scheme(request):
return request.param
def test_get_call_succeeds(url_scheme):
# Make some assertions
assert True
参数化的夹具将导致每个版本的夹具执行每个引用测试:
$ pytest
tests/test_fixture_param.py::test_get_call_succeeds[http] PASSED [ 50%]
tests/test_fixture_param.py::test_get_call_succeeds[https] PASSED [100%]
======== 2 passed in 0.01s ========
结论
与常规函数调用相比,Pytest 固定装置提供了许多生活质量改进。我建议始终更喜欢 Pytest 固定装置而不是常规函数,除非您必须能够直接调用固定装置。不直接调用pytest的fixtures,调用会失败。