首页 > 解决方案 > 何时使用 pytest 固定装置?

问题描述

我是测试新手,偶然发现了 pytest 固定装置,但我不完全确定何时使用它们以及为什么它们有用。

例如,请看下面的代码:

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

pytest.fixture 这里的作用是什么?为什么我们不能简单地创建一个函数input_value()并在测试函数中运行该函数?例如:

import pytest

def input_value():
   input = 39
   return input

def test_divisible_by_3():
   assert input_value() % 3 == 0

def test_divisible_by_6():
   assert input_value() % 6 == 0

为什么我们不能这样做?使用固定装置有什么用?

标签: python-3.xunit-testingpytestdecorator

解决方案


Pytest Fixtures 和常规函数都可用于构建测试代码并减少代码重复。George Udosen 提供的答案很好地解释了这一点。

但是,OP 专门询问了 apytest.fixture和常规 Python 函数之间的区别,并且存在许多区别:

Pytest 固定装置的范围

默认情况下,pytest.fixture为每个引用夹具的测试函数执行 a 。但是,在某些情况下,夹具设置可能在计算上很昂贵或很耗时,例如初始化数据库。为此,pytest.fixture可以为 a 配置更大的范围。这允许pytest.fixture在模块中的测试(模块范围)甚至在 pytest 运行的所有测试(会话范围)中重用。以下示例使用模块范围的固定装置来加速测试:

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 将注意为测试实例化夹具并将实例提供给测试函数。这是依赖注入的一种形式。

与常规函数相比,优势在于您可以pytest.fixture按名称引用任何函数,而无需显式导入它。例如,Pytest 带有一个tmp_path夹具,任何测试都可以使用它来处理临时文件。以下示例取自Pytest 文档

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 夹具可以参数化

测试参数化非常相似, 夹具参数化允许用户指定夹具的多个“变体”,每个变体具有不同的返回值。使用该夹具的每个测试都将执行多次,每个变体一次。假设您想测试您的所有代码是否都针对 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 固定装置,调用将失败。


推荐阅读